# HG changeset patch # User Marcin Kuzminski # Date 2012-09-02 19:19:54 # Node ID 63e58ef80ef1790b4bb6825246300623b73a4b49 # Parent 9d097c2592d39045eb578416518b02353961e3ae # Parent 5d12768a0aa191f0262a579a9b8b328273e10b6e Merge beta branch into stable diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -2,6 +2,7 @@ syntax: glob *.pyc *.swp *.sqlite +*.tox *.egg-info *.egg @@ -20,3 +21,4 @@ syntax: regexp ^RhodeCode\.egg-info$ ^rc\.ini$ ^fabfile.py +^\.rhodecode$ diff --git a/.travis.yml b/.travis.yml new file mode 100644 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,38 @@ +language: python +python: + - "2.5" + - "2.6" + - "2.7" + +env: + - TEST_DB=sqlite:////tmp/rhodecode_test.sqlite + - TEST_DB=mysql://root@127.0.0.1/rhodecode_test + - TEST_DB=postgresql://postgres@127.0.0.1/rhodecode_test + +# command to install dependencies +before_script: + - mysql -e 'create database rhodecode_test;' + - psql -c 'create database rhodecode_test;' -U postgres + - git --version + +before_install: + - sudo apt-get remove git + - sudo add-apt-repository ppa:pdoes/ppa -y + - sudo apt-get update -y + - sudo apt-get install git -y + +install: + - pip install mysql-python psycopg2 mock unittest2 + - pip install . --use-mirrors + +# command to run tests +script: nosetests + +notifications: + email: + - marcinkuz@gmail.com + irc: "irc.freenode.org#rhodecode" + +branches: + only: + - dev diff --git a/CONTRIBUTORS b/CONTRIBUTORS --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -18,4 +18,9 @@ List of contributors to RhodeCode projec Aras Pranckevicius Tony Bussieres Erwin Kroon - nansenat16 \ No newline at end of file + nansenat16 + Vincent Duvert + Takumi IINO + Indra Talip + James Rhodes + Dominik Ruf \ No newline at end of file diff --git a/README.rst b/README.rst --- a/README.rst +++ b/README.rst @@ -72,25 +72,26 @@ RhodeCode Features Each request can be logged and authenticated. - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous. Supports http/https and LDAP -- Full permissions (private/read/write/admin) and authentication per project. - One account for web interface and mercurial_ push/pull/clone operations. +- Full permissions (private/read/write/admin) for each repository, additional + explicit forking and repository permissions. - Have built in users groups for easier permission management - Repository groups let you group repos and manage them easier. - Users can fork other users repo. RhodeCode have also compare view to see combined changeset for all changeset made within single push. - Build in commit-api let's you add, edit and commit files right from RhodeCode interface using simple editor or upload form for binaries. +- Powerfull pull-request driven review system with inline commenting, and + changeset statuses, notification system. +- Importing SVN repositories from remote locations into RhodeCode. - Mako templates let's you customize the look and feel of the application. - Beautiful diffs, annotations and source code browsing all colored by pygments. - Raw diffs are made in git-diff format, including git_ binary-patches + Raw diffs are made in git-diff format, including GIT_ binary-patches - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics - Admin interface with user/permission management. Admin activity journal, logs pulls, pushes, forks, registrations and other actions made by all users. - Server side forks. It is possible to fork a project and modify it freely without breaking the main repository. You can even write Your own hooks and install them -- code review with notification system, inline commenting, all parsed using - rst syntax - rst and markdown README support for repositories - Full text search powered by Whoosh on the source files, and file names. Build in indexing daemons, with optional incremental index build @@ -110,8 +111,9 @@ Incoming / Plans ---------------- - Finer granular permissions per branch, repo group or subrepo -- pull requests and web based merges -- per line file history +- Pull requests with web based merges +- Per line file history +- Simple issue tracker - SSH based authentication with server side key management - Commit based built in wiki system - More statistics and graph (global annotation + some more statistics) @@ -131,7 +133,8 @@ Listed bellow are various support resour .. note:: - Please try to read the documentation before posting any issues + Please try to read the documentation before posting any issues, especially + the **troubleshooting section** - Join the `Google group `_ and ask any questions. diff --git a/development.ini b/development.ini --- a/development.ini +++ b/development.ini @@ -30,22 +30,31 @@ pdebug = false [server:main] ##nr of threads to spawn -threadpool_workers = 5 +#threadpool_workers = 5 ##max request before thread respawn -threadpool_max_requests = 10 +#threadpool_max_requests = 10 ##option to use threads of process -use_threadpool = true +#use_threadpool = true -use = egg:Paste#http +#use = egg:Paste#http +use = egg:waitress#main host = 0.0.0.0 port = 5000 +[filter:proxy-prefix] +# prefix middleware for rc +use = egg:PasteDeploy#prefix +prefix = / + [app:main] use = egg:rhodecode +#filter-with = proxy-prefix full_stack = true static_files = true +# Optional Languages +# en, fr, ja, pt_BR, zh_CN, zh_TW lang = en cache_dir = %(here)s/data index_dir = %(here)s/data/index @@ -54,6 +63,15 @@ cut_off_limit = 256000 force_https = false commit_parse_limit = 25 use_gravatar = true + +## alternative_gravatar_url allows you to use your own avatar server application +## the following parts of the URL will be replaced +## {email} user email +## {md5email} md5 hash of the user email (like at gravatar.com) +## {size} size of the image that is expected from the server application +#alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size} +#alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size} + container_auth_enabled = false proxypass_auth_enabled = false default_encoding = utf8 @@ -78,7 +96,8 @@ default_encoding = utf8 issue_pat = (?:\s*#)(\d+) ## server url to the issue, each {id} will be replaced with match -## fetched from the regex and {repo} is replaced with repository name +## fetched from the regex and {repo} is replaced with full repository name +## including groups {repo_name} is replaced with just name of repo issue_server_link = https://myissueserver.com/{repo}/issue/{id} @@ -165,30 +184,34 @@ beaker.cache.sql_cache_long.key_length = ## The storage uses the Container API ## that is also used by the cache system. -## db session example - +## db session ## #beaker.session.type = ext:database #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode #beaker.session.table_name = db_session -## encrypted cookie session, good for many instances +## encrypted cookie client side session, good for many instances ## #beaker.session.type = cookie -beaker.session.type = file +## file based cookies (default) ## +#beaker.session.type = file + + beaker.session.key = rhodecode -# secure cookie requires AES python libraries +## secure cookie requires AES python libraries ## #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu #beaker.session.validate_key = 9712sds2212c--zxc123 -beaker.session.timeout = 36000 +## sets session as invalid if it haven't been accessed for given amount of time +beaker.session.timeout = 2592000 beaker.session.httponly = true +#beaker.session.cookie_path = / -## uncomment for https secure cookie +## uncomment for https secure cookie ## beaker.session.secure = false -##auto save the session to not to use .save() +## auto save the session to not to use .save() ## beaker.session.auto = False -##true exire at browser close +## default cookie expiration time in seconds `true` expire at browser close ## #beaker.session.cookie_expires = 3600 diff --git a/docs/api/api.rst b/docs/api/api.rst --- a/docs/api/api.rst +++ b/docs/api/api.rst @@ -7,7 +7,7 @@ API Starting from RhodeCode version 1.2 a simple API was implemented. There's a single schema for calling all api methods. API is implemented -with JSON protocol both ways. An url to send API request in RhodeCode is +with JSON protocol both ways. An url to send API request to RhodeCode is /_admin/api API ACCESS FOR WEB VIEWS @@ -59,6 +59,47 @@ All responses from API will be `HTTP/1.0 calling api *error* key from response will contain failure description and result will be null. + +API CLIENT +++++++++++ + +From version 1.4 RhodeCode adds a script that allows to easily +communicate with API. After installing RhodeCode a `rhodecode-api` script +will be available. + +To get started quickly simply run:: + + rhodecode-api _create_config --apikey= --apihost= + +This will create a file named .config in the directory you executed it storing +json config file with credentials. You can skip this step and always provide +both of the arguments to be able to communicate with server + + +after that simply run any api command for example get_repo:: + + rhodecode-api get_repo + + calling {"api_key": "", "id": 75, "args": {}, "method": "get_repo"} to http://127.0.0.1:5000 + rhodecode said: + {'error': 'Missing non optional `repoid` arg in JSON DATA', + 'id': 75, + 'result': None} + +Ups looks like we forgot to add an argument + +Let's try again now giving the repoid as parameters:: + + rhodecode-api get_repo repoid:rhodecode + + calling {"api_key": "", "id": 39, "args": {"repoid": "rhodecode"}, "method": "get_repo"} to http://127.0.0.1:5000 + rhodecode said: + {'error': None, + 'id': 39, + 'result': } + + + API METHODS +++++++++++ @@ -76,12 +117,64 @@ INPUT:: api_key : "" method : "pull" args : { - "repo_name" : "" + "repoid" : "" + } + +OUTPUT:: + + id : + result : "Pulled from ``" + error : null + + +rescan_repos +------------ + +Dispatch rescan repositories action. If remove_obsolete is set +RhodeCode will delete repos that are in database but not in the filesystem. +This command can be executed only using api_key belonging to user with admin +rights. + +INPUT:: + + id : + api_key : "" + method : "rescan_repos" + args : { + "remove_obsolete" : "" } OUTPUT:: - result : "Pulled from " + id : + result : "{'added': [], + 'removed': []}" + error : null + + +lock +---- + +Set locking state on given repository by given user. +This command can be executed only using api_key belonging to user with admin +rights. + +INPUT:: + + id : + api_key : "" + method : "lock" + args : { + "repoid" : "" + "userid" : "", + "locked" : "" + + } + +OUTPUT:: + + id : + result : "User `` set lock state for repo `` to `true|false`" error : null @@ -104,13 +197,15 @@ INPUT:: OUTPUT:: + id : result: None if user does not exist or { - "id" : "", + "user_id" : "", "username" : "", "firstname": "", "lastname" : "", "email" : "", + "emails": "", "active" : "", "admin" :  "", "ldap_dn" : "", @@ -143,13 +238,15 @@ INPUT:: OUTPUT:: + id : result: [ { - "id" : "", + "user_id" : "", "username" : "", "firstname": "", "lastname" : "", "email" : "", + "emails": "", "active" : "", "admin" :  "", "ldap_dn" : "", @@ -174,20 +271,32 @@ INPUT:: method : "create_user" args : { "username" : "", + "email" : "", "password" : "", - "email" : "", - "firstname" : " = None", - "lastname" : " = None", - "active" : " = True", - "admin" : " = False", - "ldap_dn" : " = None" + "firstname" : " = Optional(None)", + "lastname" : " = Optional(None)", + "active" : " = Optional(True)", + "admin" : " = Optional(False)", + "ldap_dn" : " = Optional(None)" } OUTPUT:: + id : result: { - "id" : "", - "msg" : "created new user " + "msg" : "created new user ``", + "user": { + "user_id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "emails": "", + "active" : "", + "admin" :  "", + "ldap_dn" : "", + "last_login": "", + }, } error: null @@ -195,7 +304,7 @@ OUTPUT:: update_user ----------- -updates current one if such user exists. This command can +updates given user if such user exists. This command can be executed only using api_key belonging to user with admin rights. @@ -206,21 +315,60 @@ INPUT:: method : "update_user" args : { "userid" : "", - "username" : "", - "password" : "", - "email" : "", - "firstname" : "", - "lastname" : "", - "active" : "", - "admin" : "", - "ldap_dn" : "" + "username" : " = Optional", + "email" : " = Optional", + "password" : " = Optional", + "firstname" : " = Optional", + "lastname" : " = Optional", + "active" : " = Optional", + "admin" : " = Optional", + "ldap_dn" : " = Optional" } OUTPUT:: + id : result: { - "id" : "", - "msg" : "updated user " + "msg" : "updated user ID: ", + "user": { + "user_id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "emails": "", + "active" : "", + "admin" :  "", + "ldap_dn" : "", + "last_login": "", + }, + } + error: null + + +delete_user +----------- + + +deletes givenuser if such user exists. This command can +be executed only using api_key belonging to user with admin rights. + + +INPUT:: + + id : + api_key : "" + method : "delete_user" + args : { + "userid" : "", + } + +OUTPUT:: + + id : + result: { + "msg" : "deleted user ID: ", + "user": null } error: null @@ -238,25 +386,29 @@ INPUT:: api_key : "" method : "get_users_group" args : { - "group_name" : "" + "usersgroupid" : "" } OUTPUT:: + id : result : None if group not exist { - "id" : "", - "group_name" : "", - "active": "", + "users_group_id" : "", + "group_name" : "", + "active": "", "members" : [ - { "id" : "", + { + "user_id" : "", "username" : "", "firstname": "", "lastname" : "", "email" : "", + "emails": "", "active" : "", "admin" :  "", - "ldap" : "" + "ldap_dn" : "", + "last_login": "", }, … ] @@ -280,25 +432,29 @@ INPUT:: OUTPUT:: + id : result : [ { - "id" : "", - "group_name" : "", - "active": "", - "members" : [ - { - "id" : "", - "username" : "", - "firstname": "", - "lastname" : "", - "email" : "", - "active" : "", - "admin" :  "", - "ldap" : "" - }, - … - ] - } + "users_group_id" : "", + "group_name" : "", + "active": "", + "members" : [ + { + "user_id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "emails": "", + "active" : "", + "admin" :  "", + "ldap_dn" : "", + "last_login": "", + }, + … + ] + }, + … ] error : null @@ -317,14 +473,34 @@ INPUT:: method : "create_users_group" args: { "group_name": "", - "active":" = True" + "active":" = Optional(True)" } OUTPUT:: + id : result: { - "id": "", - "msg": "created new users group " + "msg": "created new users group ``", + "users_group": { + "users_group_id" : "", + "group_name" : "", + "active": "", + "members" : [ + { + "user_id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "emails": "", + "active" : "", + "admin" :  "", + "ldap_dn" : "", + "last_login": "", + }, + … + ] + }, } error: null @@ -343,16 +519,16 @@ INPUT:: api_key : "" method : "add_user_users_group" args: { - "group_name" : "", - "username" : "" + "usersgroupid" : "", + "userid" : "", } OUTPUT:: + id : result: { - "id": "", "success": True|False # depends on if member is in group - "msg": "added member to users group | + "msg": "added member `` to users group `` | User is already in that group" } error: null @@ -372,12 +548,13 @@ INPUT:: api_key : "" method : "remove_user_from_users_group" args: { - "group_name" : "", - "username" : "" + "usersgroupid" : "", + "userid" : "", } OUTPUT:: + id : result: { "success": True|False, # depends on if member is in group "msg": "removed member from users group | @@ -405,23 +582,32 @@ INPUT:: OUTPUT:: + id : result: None if repository does not exist or { - "id" : "", + "repo_id" : "", "repo_name" : "" - "type" : "", + "repo_type" : "", + "clone_uri" : "", + "private": : "", + "created_on" : "", "description" : "", + "landing_rev": "", + "owner": "", + "fork_of": "", "members" : [ { "type": "user", - "id" : "", - "username" : "", - "firstname": "", - "lastname" : "", - "email" : "", - "active" : "", - "admin" :  "", - "ldap" : "", + "user_id" : "", + "username" : "", + "firstname": "", + "lastname" : "", + "email" : "", + "emails": "", + "active" : "", + "admin" :  "", + "ldap_dn" : "", + "last_login": "", "permission" : "repository.(read|write|admin)" }, … @@ -454,12 +640,19 @@ INPUT:: OUTPUT:: + id : result: [ { - "id" : "", + "repo_id" : "", "repo_name" : "" - "type" : "", - "description" : "" + "repo_type" : "", + "clone_uri" : "", + "private": : "", + "created_on" : "", + "description" : "", + "landing_rev": "", + "owner": "", + "fork_of": "", }, … ] @@ -481,14 +674,15 @@ INPUT:: api_key : "" method : "get_repo_nodes" args: { - "repo_name" : "", + "repoid" : "" "revision" : "", "root_path" : "", - "ret_type" : "" = 'all' + "ret_type" : " = Optional('all')" } OUTPUT:: + id : result: [ { "name" : "" @@ -516,18 +710,31 @@ INPUT:: method : "create_repo" args: { "repo_name" : "", - "owner_name" : "", - "description" : " = ''", - "repo_type" : " = 'hg'", - "private" : " = False", - "clone_uri" : " = None", + "owner" : "", + "repo_type" : "", + "description" : " = Optional('')", + "private" : " = Optional(False)", + "clone_uri" : " = Optional(None)", + "landing_rev" : " = Optional('tip')", } OUTPUT:: + id : result: { - "id": "", - "msg": "Created new repository ", + "msg": "Created new repository ``", + "repo": { + "repo_id" : "", + "repo_name" : "" + "repo_type" : "", + "clone_uri" : "", + "private": : "", + "created_on" : "", + "description" : "", + "landing_rev": "", + "owner": "", + "fork_of": "", + }, } error: null @@ -545,13 +752,15 @@ INPUT:: api_key : "" method : "delete_repo" args: { - "repo_name" : "", + "repoid" : "" } OUTPUT:: + id : result: { - "msg": "Deleted repository ", + "msg": "Deleted repository ``", + "success": true } error: null @@ -570,15 +779,17 @@ INPUT:: api_key : "" method : "grant_user_permission" args: { - "repo_name" : "", - "username" : "", + "repoid" : "" + "userid" : "" "perm" : "(repository.(none|read|write|admin))", } OUTPUT:: + id : result: { - "msg" : "Granted perm: for user: in repo: " + "msg" : "Granted perm: `` for user: `` in repo: ``", + "success": true } error: null @@ -596,14 +807,16 @@ INPUT:: api_key : "" method : "revoke_user_permission" args: { - "repo_name" : "", - "username" : "", + "repoid" : "" + "userid" : "" } OUTPUT:: + id : result: { - "msg" : "Revoked perm for user: in repo: " + "msg" : "Revoked perm for user: `` in repo: ``", + "success": true } error: null @@ -622,15 +835,17 @@ INPUT:: api_key : "" method : "grant_users_group_permission" args: { - "repo_name" : "", - "group_name" : "", + "repoid" : "" + "usersgroupid" : "" "perm" : "(repository.(none|read|write|admin))", } OUTPUT:: + id : result: { - "msg" : "Granted perm: for group: in repo: " + "msg" : "Granted perm: `` for group: `` in repo: ``", + "success": true } error: null @@ -647,13 +862,15 @@ INPUT:: api_key : "" method : "revoke_users_group_permission" args: { - "repo_name" : "", - "users_group" : "", + "repoid" : "" + "usersgroupid" : "" } OUTPUT:: + id : result: { - "msg" : "Revoked perm for group: in repo: " + "msg" : "Revoked perm for group: `` in repo: ``", + "success": true } error: null \ No newline at end of file diff --git a/docs/changelog.rst b/docs/changelog.rst --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,66 @@ Changelog ========= +1.4.0 (**2012-09-03**) +---------------------- + +news +++++ + +- new codereview system +- email map, allowing users to have multiple email addresses mapped into + their accounts +- improved git-hook system. Now all actions for git are logged into journal + including pushed revisions, user and IP address +- changed setup-app into setup-rhodecode and added default options to it. +- new git repos are created as bare now by default +- #464 added links to groups in permission box +- #465 mentions autocomplete inside comments boxes +- #469 added --update-only option to whoosh to re-index only given list + of repos in index +- rhodecode-api CLI client +- new git http protocol replaced buggy dulwich implementation. + Now based on pygrack & gitweb +- Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and + reformated based on user suggestions. Additional rss/atom feeds for user + journal +- various i18n improvements +- #478 permissions overview for admin in user edit view +- File view now displays small gravatars off all authors of given file +- Implemented landing revisions. Each repository will get landing_rev attribute + that defines 'default' revision/branch for generating readme files +- Implemented #509, RhodeCode enforces SSL for push/pulling if requested at + earliest possible call. +- Import remote svn repositories to mercurial using hgsubversion. +- Fixed #508 RhodeCode now has a option to explicitly set forking permissions +- RhodeCode can use alternative server for generating avatar icons +- implemented repositories locking. Pull locks, push unlocks. Also can be done + via API calls +- #538 form for permissions can handle multiple users at once + +fixes ++++++ + +- improved translations +- fixes issue #455 Creating an archive generates an exception on Windows +- fixes #448 Download ZIP archive keeps file in /tmp open and results + in out of disk space +- fixes issue #454 Search results under Windows include proceeding + backslash +- fixed issue #450. Rhodecode no longer will crash when bad revision is + present in journal data. +- fix for issue #417, git execution was broken on windows for certain + commands. +- fixed #413. Don't disable .git directory for bare repos on deleting +- fixed issue #459. Changed the way of obtaining logger in reindex task. +- fixed #453 added ID field in whoosh SCHEMA that solves the issue of + reindexing modified files +- fixed #481 rhodecode emails are sent without Date header +- fixed #458 wrong count when no repos are present +- fixed issue #492 missing `\ No newline at end of file` test at the end of + new chunk in html diff +- full text search now works also for commit messages + 1.3.6 (**2012-05-17**) ---------------------- diff --git a/docs/conf.py b/docs/conf.py --- a/docs/conf.py +++ b/docs/conf.py @@ -54,8 +54,8 @@ copyright = u'%s, Marcin Kuzminski' % (d # The short X.Y version. root = os.path.dirname(os.path.dirname(__file__)) sys.path.append(root) -from rhodecode import get_version, __version__ -version = get_version() +from rhodecode import __version__ +version = __version__ # The full version, including alpha/beta/rc tags. release = __version__ diff --git a/docs/contributing.rst b/docs/contributing.rst --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -27,7 +27,8 @@ enviroment. After finishing your changes make sure all tests passes ok. You can run -the testsuite running nosetest from the project root. +the testsuite running ``nosetest`` from the project root, or if you use tox +run tox for python2.5-2.7 with multiple database test. | Thank you for any contributions! | Marcin diff --git a/docs/index.rst b/docs/index.rst --- a/docs/index.rst +++ b/docs/index.rst @@ -21,9 +21,12 @@ Users Guide usage/general usage/git_support + usage/performance + usage/locking usage/statistics usage/backup usage/debugging + usage/troubleshooting **Develop** diff --git a/docs/installation.rst b/docs/installation.rst --- a/docs/installation.rst +++ b/docs/installation.rst @@ -25,13 +25,18 @@ Or:: pip install rhodecode If you prefer to install RhodeCode manually simply grab latest release from -http://pypi.python.org/pypi/rhodecode, decompress the archive and run:: +http://pypi.python.org/pypi/RhodeCode, decompress the archive and run:: python setup.py install +Step by step installation example for Windows +--------------------------------------------- -Step by step installation example ---------------------------------- +:ref:`installation_win` + + +Step by step installation example for Linux +------------------------------------------- For installing RhodeCode i highly recommend using separate virtualenv_. This @@ -41,7 +46,7 @@ python and making things less problemati - Assuming you have installed virtualenv_ create a new virtual environment using virtualenv command:: - virtualenv --no-site-packages /var/www/rhodecode-venv + virtualenv --no-site-packages /opt/rhodecode-venv .. note:: Using ``--no-site-packages`` when generating your @@ -54,10 +59,10 @@ python and making things less problemati Python's "main" site-packages dir. -- this will install new virtualenv_ into `/var/www/rhodecode-venv`. +- this will install new virtualenv_ into `/opt/rhodecode-venv`. - Activate the virtualenv_ by running:: - source /var/www/rhodecode-venv/bin/activate + source /opt/rhodecode-venv/bin/activate .. note:: If you're using UNIX, *do not* use ``sudo`` to run the ``virtualenv`` script. It's perfectly acceptable (and desirable) @@ -66,7 +71,7 @@ python and making things less problemati - Make a folder for rhodecode data files, and configuration somewhere on the filesystem. For example:: - mkdir /var/www/rhodecode + mkdir /opt/rhodecode - Go into the created directory run this command to install rhodecode:: diff --git a/docs/installation_win.rst b/docs/installation_win.rst new file mode 100644 --- /dev/null +++ b/docs/installation_win.rst @@ -0,0 +1,244 @@ +.. _installation_win: + + +Step by step Installation for Windows +===================================== + + +RhodeCode step-by-step install Guide for Windows + +Target OS: Windows XP SP3 English (Clean installation) ++ All Windows Updates until 24-may-2012 + +Step1 - Install Visual Studio 2008 Express +------------------------------------------ + + +Optional: You can also install MingW, but VS2008 installation is easier + +Download "Visual C++ 2008 Express Edition with SP1" from: +http://www.microsoft.com/visualstudio/en-us/products/2008-editions/express +(if not found or relocated, google for "visual studio 2008 express" for +updated link) + +You can also download full ISO file for offline installation, just +choose "All - Offline Install ISO image file" in the previous page and +choose "Visual C++ 2008 Express" when installing. + + +.. note:: + + Silverlight Runtime and SQL Server 2008 Express Edition are not + required, you can uncheck them + + +Step2 - Install Python +---------------------- + +Install Python 2.x.y (x >= 5) x86 version (32bit). DO NOT USE A 3.x version. +Download Python 2.x.y from: +http://www.python.org/download/ + +Choose "Windows Installer" (32bit version) not "Windows X86-64 +Installer". While writing this guide, the latest version was v2.7.3. +Remember the specific major and minor version installed, because it will +be needed in the next step. In this case, it is "2.7". + + +Step3 - Install Win32py extensions +---------------------------------- + +Download pywin32 from: +http://sourceforge.net/projects/pywin32/files/ + +- Click on "pywin32" folder +- Click on the first folder (in this case, Build 217, maybe newer when you try) +- Choose the file ending with ".win32-py2.x.exe" -> x being the minor + version of Python you installed (in this case, 7) + When writing this guide, the file was: + http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/pywin32-217.win32-py2.7.exe/download + + +Step4 - Python BIN +------------------ + +Add Python BIN folder to the path + +You have to add the Python folder to the path, you can do it manually +(editing "PATH" environment variable) or using Windows Support Tools +that came preinstalled in Vista/7 and can be installed in Windows XP. + +- Using support tools on WINDOWS XP: + If you use Windows XP you can install them using Windows XP CD and + navigating to \SUPPORT\TOOLS. There, execute Setup.EXE (not MSI). + Afterwards, open a CMD and type:: + + SETX PATH "%PATH%;[your-python-path]" -M + + Close CMD (the path variable will be updated then) + +- Using support tools on WINDOWS Vista/7: + + Open a CMD and type:: + + SETX PATH "%PATH%;[your-python-path]" /M + + Please substitute [your-python-path] with your Python installation path. + Typically: C:\\Python27 + + +Step5 - RhodeCode folder structure +---------------------------------- + +Create a RhodeCode folder structure + +This is only a example to install RhodeCode, you can of course change +it. However, this guide will follow the proposed structure, so please +later adapt the paths if you change them. My recommendation is to use +folders with NO SPACES. But you can try if you are brave... + +Create the following folder structure:: + + C:\RhodeCode + C:\RhodeCode\Bin + C:\RhodeCode\Env + C:\RhodeCode\Repos + + +Step6 - Install virtualenv +--------------------------- + +Install Virtual Env for Python + +Navigate to: http://www.virtualenv.org/en/latest/index.html#installation +Right click on "virtualenv.py" file and choose "Save link as...". +Download to C:\\RhodeCode (or whatever you want) +(the file is located at +https://raw.github.com/pypa/virtualenv/master/virtualenv.py) + +Create a virtual Python environment in C:\\RhodeCode\\Env (or similar). To +do so, open a CMD (Python Path should be included in Step3), navigate +where you downloaded "virtualenv.py", and write:: + + python virtualenv.py C:\RhodeCode\Env + +(--no-site-packages is now the default behaviour of virtualenv, no need +to include it) + + +Step7 - Install RhodeCode +------------------------- + +Finally, install RhodeCode + +Close previously opened command prompt/s, and open a Visual Studio 2008 +Command Prompt (**IMPORTANT!!**). To do so, go to Start Menu, and then open +"Microsoft Visual C++ 2008 Express Edition" -> "Visual Studio Tools" -> +"Visual Studio 2008 Command Prompt" + +In that CMD (loaded with VS2008 PATHs) type:: + + cd C:\RhodeCode\Env\Scripts (or similar) + activate + +The prompt will change into "(Env) C:\\RhodeCode\\Env\\Scripts" or similar +(depending of your folder structure). Then type:: + + pip install rhodecode + +(long step, please wait until fully complete) + +Some warnings will appear, don't worry as they are normal. + + +Step8 - Configuring RhodeCode +----------------------------- + + +steps taken from http://packages.python.org/RhodeCode/setup.html + +You have to use the same Visual Studio 2008 command prompt as Step7, so +if you closed it reopen it following the same commands (including the +"activate" one). When ready, just type:: + + cd C:\RhodeCode\Bin + paster make-config RhodeCode production.ini + +Then, you must edit production.ini to fit your needs (ip address, ip +port, mail settings, database, whatever). I recommend using NotePad++ +(free) or similar text editor, as it handles well the EndOfLine +character differences between Unix and Windows +(http://notepad-plus-plus.org/) + +For the sake of simplicity lets run it with the default settings. After +your edits (if any), in the previous Command Prompt, type:: + + paster setup-rhodecode production.ini + +(this time a NEW database will be installed, you must follow a different +step to later UPGRADE to a newer RhodeCode version) + +The script will ask you for confirmation about creating a NEW database, +answer yes (y) +The script will ask you for repository path, answer C:\\RhodeCode\\Repos +(or similar) +The script will ask you for admin username and password, answer "admin" ++ "123456" (or whatever you want) +The script will ask you for admin mail, answer "admin@xxxx.com" (or +whatever you want) + +If you make some mistake and the script does not end, don't worry, start +it again. + + +Step9 - Running RhodeCode +------------------------- + + +In the previous command prompt, being in the C:\\RhodeCode\\Bin folder, +just type:: + + paster serve production.ini + +Open yout web server, and go to http://127.0.0.1:5000 + +It works!! :-) + +Remark: +If it does not work first time, just Ctrl-C the CMD process and start it +again. Don't forget the "http://" in Internet Explorer + + + +What this Guide does not cover: + +- Installing Celery +- Running RhodeCode as Windows Service. You can investigate here: + + - http://pypi.python.org/pypi/wsgisvc + - http://ryrobes.com/python/running-python-scripts-as-a-windows-service/ + - http://wiki.pylonshq.com/display/pylonscookbook/How+to+run+Pylons+as+a+Windows+service + +- Using Apache. You can investigate here: + + - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4 + + +Upgrading +========= + +Stop running RhodeCode +Open a CommandPrompt like in Step7 (VS2008 path + activate) and type:: + + easy_install -U rhodecode + cd \RhodeCode\Bin + +{ backup your production.ini file now} :: + + paster make-config RhodeCode production.ini + +(check changes and update your production.ini accordingly) :: + + paster upgrade-db production.ini (update database) + +Full steps in http://packages.python.org/RhodeCode/upgrade.html \ No newline at end of file diff --git a/docs/setup.rst b/docs/setup.rst --- a/docs/setup.rst +++ b/docs/setup.rst @@ -34,6 +34,11 @@ entering this "root" path ``setup-rhodec and password for the initial admin account which ``setup-rhodecode`` sets up for you. +setup process can be fully automated, example for lazy:: + + paster setup-rhodecode production.ini --user=marcink --password=secret --email=marcin@rhodecode.org --repos=/home/marcink/my_repos + + - The ``setup-rhodecode`` command will create all of the needed tables and an admin account. When choosing a root path you can either use a new empty location, or a location which already contains existing repositories. If you @@ -527,6 +532,18 @@ Sample config for nginx using proxy:: access_log /var/log/nginx/rhodecode.access.log; error_log /var/log/nginx/rhodecode.error.log; + # uncomment if you have nginx with chunking module compiled + # fixes the issues of having to put postBuffer data for large git + # pushes + #chunkin on; + #error_page 411 = @my_411_error; + #location @my_411_error { + # chunkin_resume; + #} + + # uncomment if you want to serve static files by nginx + #root /path/to/installation/rhodecode/public; + location / { try_files $uri @rhode; } @@ -682,43 +699,9 @@ environment. Other configuration files ------------------------- -Some example init.d scripts can be found here, for debian and gentoo: - -https://rhodecode.org/rhodecode/files/tip/init.d - - -Troubleshooting ---------------- - -:Q: **Missing static files?** -:A: Make sure either to set the `static_files = true` in the .ini file or - double check the root path for your http setup. It should point to - for example: - /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public - -| - -:Q: **Can't install celery/rabbitmq** -:A: Don't worry RhodeCode works without them too. No extra setup is required. +Some example init.d scripts can be found in init.d directory:: -| - -:Q: **Long lasting push timeouts?** -:A: Make sure you set a longer timeouts in your proxy/fcgi settings, timeouts - are caused by https server and not RhodeCode. - -| - -:Q: **Large pushes timeouts?** -:A: Make sure you set a proper max_body_size for the http server. - -| - -:Q: **Apache doesn't pass basicAuth on pull/push?** -:A: Make sure you added `WSGIPassAuthorization true`. - -For further questions search the `Issues tracker`_, or post a message in the -`google group rhodecode`_ + https://secure.rhodecode.org/rhodecode/files/beta/init.d .. _virtualenv: http://pypi.python.org/pypi/virtualenv .. _python: http://www.python.org/ @@ -729,4 +712,4 @@ For further questions search the `Issues .. _mercurial-server: http://www.lshift.net/mercurial-server.html .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues -.. _google group rhodecode: http://groups.google.com/group/rhodecode +.. _google group rhodecode: http://groups.google.com/group/rhodecode \ No newline at end of file diff --git a/docs/usage/general.rst b/docs/usage/general.rst --- a/docs/usage/general.rst +++ b/docs/usage/general.rst @@ -62,7 +62,6 @@ the _ syntax can be used anywhere in for changelogs, files and other can be exchanged with _ syntax. - Mailing ------- @@ -82,4 +81,27 @@ Trending source files Trending source files are calculated based on pre defined dict of known types and extensions. If You miss some extension or Would like to scan some custom files it's possible to add new types in `LANGUAGES_EXTENSIONS_MAP` dict -located in `/rhodecode/lib/celerylib/tasks.py` \ No newline at end of file +located in `/rhodecode/lib/celerylib/tasks.py` + + +Cloning remote repositories +--------------------------- + +RhodeCode has an ability to clone remote repos from given remote locations. +Currently it support following options: + +- hg -> hg clone +- svn -> hg clone +- git -> git clone + + +.. note:: + + - *`svn -> hg` cloning requires `hgsubversion` library to be installed.* + +If you need to clone repositories that are protected via basic auth, you +might pass the url with stored credentials inside eg. +`http://user:passw@remote.server/repo, RhodeCode will try to login and clone +using given credentials. Please take a note that they will be stored as +plaintext inside the database. RhodeCode will remove auth info when showing the +clone url in summary page. diff --git a/docs/usage/git_support.rst b/docs/usage/git_support.rst --- a/docs/usage/git_support.rst +++ b/docs/usage/git_support.rst @@ -5,11 +5,13 @@ GIT support =========== -Git support in RhodeCode 1.3 was enabled by default. +Git support in RhodeCode 1.3 was enabled by default. You need to have a git +client installed on the machine to make git fully work. + Although There are some limitations on git usage. -- No hooks are runned for git push/pull actions. -- logs in action journals don't have git operations +- hooks that are executed on pull/push are not *real* hooks, they are + just emulating the behavior, and are executed **BEFORE** action takes place. - large pushes needs http server with chunked encoding support. if you plan to use git you need to run RhodeCode with some @@ -17,14 +19,19 @@ http server that supports chunked encodi i recommend using waitress_ or gunicorn_ (linux only) for `paste` wsgi app replacement. -To use waitress simply change change the following in the .ini file:: +To use, simply change change the following in the .ini file:: use = egg:Paste#http -To:: +to:: use = egg:waitress#main +or:: + + use = egg:gunicorn#main + + And comment out bellow options:: threadpool_workers = diff --git a/docs/usage/locking.rst b/docs/usage/locking.rst new file mode 100644 --- /dev/null +++ b/docs/usage/locking.rst @@ -0,0 +1,41 @@ +.. _locking: + +=================================== +RhodeCode repository locking system +=================================== + + +| Repos with **locking function=disabled** is the default, that's how repos work + today. +| Repos with **locking function=enabled** behaves like follows: + +Repos have a state called `locked` that can be true or false. +The hg/git commands `hg/git clone`, `hg/git pull`, and `hg/git push` +influence this state: + +- The command `hg/git pull ` will lock that repo (locked=true) + if the user has write/admin permissions on this repo + +- The command `hg/git clone ` will lock that repo (locked=true) if the + user has write/admin permissions on this repo + + +RhodeCode will remember the user id who locked the repo +only this specific user can unlock the repo (locked=false) by calling + +- `hg/git push ` + +every other command on that repo from this user and +every command from any other user will result in http return code 423 (locked) + + +additionally the http error includes the that locked the repo +(e.g. “repository locked by user ”) + + +So the scenario of use for repos with `locking function` enabled is that +every initial clone and every pull gives users (with write permission) +the exclusive right to do a push. + + +Each repo can be manually unlocked by admin from the repo settings menu. \ No newline at end of file diff --git a/docs/usage/performance.rst b/docs/usage/performance.rst new file mode 100644 --- /dev/null +++ b/docs/usage/performance.rst @@ -0,0 +1,50 @@ +.. _performance: + +================================ +Optimizing RhodeCode Performance +================================ + +When serving large amount of big repositories RhodeCode can start +performing slower than expected. Because of demanding nature of handling large +amount of data from version control systems here are some tips how to get +the best performance. + +* RhodeCode will perform better on machines with faster disks (SSD/SAN). It's + more important to have faster disk than faster CPU. + +* Slowness on initial page can be easily fixed by grouping repositories, and/or + increasing cache size (see below) + + +Follow these few steps to improve performance of RhodeCode system. + + +1. Increase cache + + in the .ini file:: + + beaker.cache.sql_cache_long.expire=3600 <-- set this to higher number + + This option affects the cache expiration time for main page. Having + few hundreds of repositories on main page can sometimes make the system + to behave slow when cache expires for all of them. Increasing `expire` + option to day (86400) or a week (604800) will improve general response + times for the main page. RhodeCode has an intelligent cache expiration + system and it will expire cache for repositories that had been changed. + +2. Switch from sqlite to postgres or mysql + + sqlite is a good option when having small load on the system. But due to + locking issues with sqlite, it's not recommended to use it for larger + setup. Switching to mysql or postgres will result in a immediate + performance increase. + +3. Scale RhodeCode horizontally + + - running two or more instances on the same server can speed up things a lot + - load balance using round robin or ip hash + - you need to handle consistent user session storage by switching to + db sessions, client side sessions or sharing session data folder across + instances. See http://beaker.readthedocs.org/ docs for details. + - remember that each instance needs it's own .ini file and unique + `instance_id` set in them \ No newline at end of file diff --git a/docs/usage/troubleshooting.rst b/docs/usage/troubleshooting.rst new file mode 100644 --- /dev/null +++ b/docs/usage/troubleshooting.rst @@ -0,0 +1,70 @@ +.. _troubleshooting: + + +=============== +Troubleshooting +=============== + +:Q: **Missing static files?** +:A: Make sure either to set the `static_files = true` in the .ini file or + double check the root path for your http setup. It should point to + for example: + /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public + +| + +:Q: **Can't install celery/rabbitmq?** +:A: Don't worry RhodeCode works without them too. No extra setup is required. + Try out great celery docs for further help. + +| + +:Q: **Long lasting push timeouts?** +:A: Make sure you set a longer timeouts in your proxy/fcgi settings, timeouts + are caused by https server and not RhodeCode. + +| + +:Q: **Large pushes timeouts?** +:A: Make sure you set a proper max_body_size for the http server. Very often + Apache, Nginx or other http servers kill the connection due to to large + body. + +| + +:Q: **Apache doesn't pass basicAuth on pull/push?** +:A: Make sure you added `WSGIPassAuthorization true`. + +| + +:Q: **Git fails on push/pull?** +:A: Make sure you're using an wsgi http server that can handle chunked encoding + such as `waitress` or `gunicorn` + +| + +:Q: **How i use hooks in RhodeCode?** +:A: It's easy if they are python hooks just use advanced link in hooks section + in Admin panel, that works only for Mercurial. If you want to use githooks, + just install proper one in repository eg. create file in + `/gitrepo/hooks/pre-receive`. You can also use RhodeCode-extensions to + connect to callback hooks, for both Git and Mercurial. + +| + +:Q: **RhodeCode is slow for me, how can i make it faster?** +:A: See the :ref:`performance` section + +For further questions search the `Issues tracker`_, or post a message in the +`google group rhodecode`_ + +.. _virtualenv: http://pypi.python.org/pypi/virtualenv +.. _python: http://www.python.org/ +.. _mercurial: http://mercurial.selenic.com/ +.. _celery: http://celeryproject.org/ +.. _rabbitmq: http://www.rabbitmq.com/ +.. _python-ldap: http://www.python-ldap.org/ +.. _mercurial-server: http://www.lshift.net/mercurial-server.html +.. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories +.. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues +.. _google group rhodecode: http://groups.google.com/group/rhodecode \ No newline at end of file diff --git a/init.d/supervisord.conf b/init.d/supervisord.conf new file mode 100644 --- /dev/null +++ b/init.d/supervisord.conf @@ -0,0 +1,51 @@ +; RhodeCode Supervisord +; ########################## +; for help see http://supervisord.org/configuration.html +; ########################## + +[inet_http_server] ; inet (TCP) server disabled by default +port=127.0.0.1:9001 ; (ip_address:port specifier, *:port for all iface) +;username=user ; (default is no username (open server)) +;password=123 ; (default is no password (open server)) + +[supervisord] +logfile=/%(here)s/supervisord_rhodecode.log ; (main log file;default $CWD/supervisord.log) +logfile_maxbytes=50MB ; (max main logfile bytes b4 rotation;default 50MB) +logfile_backups=10 ; (num of main logfile rotation backups;default 10) +loglevel=info ; (log level;default info; others: debug,warn,trace) +pidfile=/%(here)s/supervisord_rhodecode.pid ; (supervisord pidfile;default supervisord.pid) +nodaemon=true ; (start in foreground if true;default false) +minfds=1024 ; (min. avail startup file descriptors;default 1024) +minprocs=200 ; (min. avail process descriptors;default 200) +umask=022 ; (process file creation umask;default 022) +user=marcink ; (default is current user, required if root) +;identifier=supervisor ; (supervisord identifier, default is 'supervisor') +;directory=/tmp ; (default is not to cd during start) +;nocleanup=true ; (don't clean up tempfiles at start;default false) +;childlogdir=/tmp ; ('AUTO' child log dir, default $TEMP) +environment=HOME=/home/marcink ; (key value pairs to add to environment) +;strip_ansi=false ; (strip ansi escape codes in logs; def. false) + +; the below section must remain in the config file for RPC +; (supervisorctl/web interface) to work, additional interfaces may be +; added by defining them in separate rpcinterface: sections +[rpcinterface:supervisor] +supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface + +[supervisorctl] +serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket +;username=user ; should be same as http_username if set +;password=123 ; should be same as http_password if set +;prompt=mysupervisor ; cmd line prompt (default "supervisor") +;history_file=~/.sc_history ; use readline history if available + + +; restart with supervisorctl restart rhodecode:* +[program:rhodecode] +numprocs = 1 +numprocs_start = 5000 # possible should match ports +directory=/home/marcink/rhodecode-dir +command = /home/marcink/v-env/bin/paster serve rc.ini +process_name = %(program_name)s_%(process_num)04d +redirect_stderr=true +stdout_logfile=/%(here)s/rhodecode.log \ No newline at end of file diff --git a/production.ini b/production.ini --- a/production.ini +++ b/production.ini @@ -30,22 +30,31 @@ pdebug = false [server:main] ##nr of threads to spawn -threadpool_workers = 5 +#threadpool_workers = 5 ##max request before thread respawn -threadpool_max_requests = 10 +#threadpool_max_requests = 10 ##option to use threads of process -use_threadpool = true +#use_threadpool = true -use = egg:Paste#http +#use = egg:Paste#http +use = egg:waitress#main host = 127.0.0.1 port = 8001 +[filter:proxy-prefix] +# prefix middleware for rc +use = egg:PasteDeploy#prefix +prefix = / + [app:main] use = egg:rhodecode +#filter-with = proxy-prefix full_stack = true static_files = true +# Optional Languages +# en, fr, ja, pt_BR, zh_CN, zh_TW lang = en cache_dir = %(here)s/data index_dir = %(here)s/data/index @@ -54,6 +63,15 @@ cut_off_limit = 256000 force_https = false commit_parse_limit = 50 use_gravatar = true + +## alternative_gravatar_url allows you to use your own avatar server application +## the following parts of the URL will be replaced +## {email} user email +## {md5email} md5 hash of the user email (like at gravatar.com) +## {size} size of the image that is expected from the server application +#alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size} +#alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size} + container_auth_enabled = false proxypass_auth_enabled = false default_encoding = utf8 @@ -78,7 +96,8 @@ default_encoding = utf8 issue_pat = (?:\s*#)(\d+) ## server url to the issue, each {id} will be replaced with match -## fetched from the regex and {repo} is replaced with repository name +## fetched from the regex and {repo} is replaced with full repository name +## including groups {repo_name} is replaced with just name of repo issue_server_link = https://myissueserver.com/{repo}/issue/{id} @@ -165,30 +184,34 @@ beaker.cache.sql_cache_long.key_length = ## The storage uses the Container API ## that is also used by the cache system. -## db session example - +## db session ## #beaker.session.type = ext:database #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode #beaker.session.table_name = db_session -## encrypted cookie session, good for many instances +## encrypted cookie client side session, good for many instances ## #beaker.session.type = cookie -beaker.session.type = file +## file based cookies (default) ## +#beaker.session.type = file + + beaker.session.key = rhodecode -# secure cookie requires AES python libraries +## secure cookie requires AES python libraries ## #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu #beaker.session.validate_key = 9712sds2212c--zxc123 -beaker.session.timeout = 36000 +## sets session as invalid if it haven't been accessed for given amount of time +beaker.session.timeout = 2592000 beaker.session.httponly = true +#beaker.session.cookie_path = / -## uncomment for https secure cookie +## uncomment for https secure cookie ## beaker.session.secure = false -##auto save the session to not to use .save() +## auto save the session to not to use .save() ## beaker.session.auto = False -##true exire at browser close +## default cookie expiration time in seconds `true` expire at browser close ## #beaker.session.cookie_expires = 3600 diff --git a/requires.txt b/requires.txt --- a/requires.txt +++ b/requires.txt @@ -1,18 +1,20 @@ +waitress==0.8.1 +webob==1.0.8 Pylons==1.0.0 -Beaker==1.6.3 +Beaker==1.6.4 WebHelpers==1.3 formencode==1.2.4 -SQLAlchemy==0.7.6 -Mako==0.7.0 -pygments>=1.4 +SQLAlchemy==0.7.8 +Mako==0.7.2 +pygments>=1.5 whoosh>=2.4.0,<2.5 celery>=2.2.5,<2.3 babel python-dateutil>=1.5.0,<2.0.0 dulwich>=0.8.5,<0.9.0 -webob==1.0.8 markdown==2.1.1 docutils==0.8.1 simplejson==2.5.2 +mock py-bcrypt -mercurial>=2.2.1,<2.3 \ No newline at end of file +mercurial>=2.3.0,<2.4 \ No newline at end of file diff --git a/rhodecode/__init__.py b/rhodecode/__init__.py --- a/rhodecode/__init__.py +++ b/rhodecode/__init__.py @@ -26,7 +26,7 @@ import sys import platform -VERSION = (1, 3, 6) +VERSION = (1, 4, 0) try: from rhodecode.lib import get_current_revision @@ -38,50 +38,19 @@ except ImportError: __version__ = ('.'.join((str(each) for each in VERSION[:3])) + '.'.join(VERSION[3:])) -__dbversion__ = 5 # defines current db version for migrations +__dbversion__ = 6 # defines current db version for migrations __platform__ = platform.system() __license__ = 'GPLv3' __py_version__ = sys.version_info +__author__ = 'Marcin Kuzminski' +__url__ = 'http://rhodecode.org' PLATFORM_WIN = ('Windows') -PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS') +PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS') #depracated is_windows = __platform__ in PLATFORM_WIN -is_unix = __platform__ in PLATFORM_OTHERS +is_unix = not is_windows -requirements = [ - "Pylons==1.0.0", - "Beaker==1.6.3", - "WebHelpers==1.3", - "formencode==1.2.4", - "SQLAlchemy==0.7.6", - "Mako==0.7.0", - "pygments>=1.4", - "whoosh>=2.4.0,<2.5", - "celery>=2.2.5,<2.3", - "babel", - "python-dateutil>=1.5.0,<2.0.0", - "dulwich>=0.8.5,<0.9.0", - "webob==1.0.8", - "markdown==2.1.1", - "docutils==0.8.1", - "simplejson==2.5.2", -] - -if __py_version__ < (2, 6): - requirements.append("pysqlite") - -if is_windows: - requirements.append("mercurial>=2.2.1,<2.3") -else: - requirements.append("py-bcrypt") - requirements.append("mercurial>=2.2.1,<2.3") - - -def get_version(): - """Returns shorter version (digit parts only) as string.""" - - return '.'.join((str(each) for each in VERSION[:3])) BACKENDS = { 'hg': 'Mercurial repository', diff --git a/rhodecode/bin/__init__.py b/rhodecode/bin/__init__.py new file mode 100644 diff --git a/rhodecode/bin/rhodecode_api.py b/rhodecode/bin/rhodecode_api.py new file mode 100755 --- /dev/null +++ b/rhodecode/bin/rhodecode_api.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.bin.backup_manager + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Api CLI client for RhodeCode + + :created_on: Jun 3, 2012 + :author: marcink + :copyright: (C) 2010-2012 Marcin Kuzminski + :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 . + +from __future__ import with_statement +import os +import sys +import random +import urllib2 +import pprint +import argparse + +try: + from rhodecode.lib.ext_json import json +except ImportError: + try: + import simplejson as json + except ImportError: + import json + + +CONFIG_NAME = '.rhodecode' +FORMAT_PRETTY = 'pretty' +FORMAT_JSON = 'json' + + +class RcConf(object): + """ + RhodeCode config for API + + conf = RcConf() + conf['key'] + + """ + + def __init__(self, config_location=None, autoload=True, autocreate=False, + config=None): + self._conf_name = CONFIG_NAME if not config_location else config_location + self._conf = {} + if autocreate: + self.make_config(config) + if autoload: + self._conf = self.load_config() + + def __getitem__(self, key): + return self._conf[key] + + def __nonzero__(self): + if self._conf: + return True + return False + + def __eq__(self): + return self._conf.__eq__() + + def __repr__(self): + return 'RcConf<%s>' % self._conf.__repr__() + + def make_config(self, config): + """ + Saves given config as a JSON dump in the _conf_name location + + :param config: + :type config: + """ + update = False + if os.path.exists(self._conf_name): + update = True + with open(self._conf_name, 'wb') as f: + json.dump(config, f, indent=4) + + if update: + sys.stdout.write('Updated config in %s\n' % self._conf_name) + else: + sys.stdout.write('Created new config in %s\n' % self._conf_name) + + def update_config(self, new_config): + """ + Reads the JSON config updates it's values with new_config and + saves it back as JSON dump + + :param new_config: + """ + config = {} + try: + with open(self._conf_name, 'rb') as conf: + config = json.load(conf) + except IOError, e: + sys.stderr.write(str(e) + '\n') + + config.update(new_config) + self.make_config(config) + + def load_config(self): + """ + Loads config from file and returns loaded JSON object + """ + try: + with open(self._conf_name, 'rb') as conf: + return json.load(conf) + except IOError, e: + #sys.stderr.write(str(e) + '\n') + pass + + +def api_call(apikey, apihost, format, method=None, **kw): + """ + Api_call wrapper for RhodeCode + + :param apikey: + :param apihost: + :param format: formatting, pretty means prints and pprint of json + json returns unparsed json + :param method: + """ + def _build_data(random_id): + """ + Builds API data with given random ID + + :param random_id: + :type random_id: + """ + return { + "id": random_id, + "api_key": apikey, + "method": method, + "args": kw + } + + if not method: + raise Exception('please specify method name !') + id_ = random.randrange(1, 9999) + req = urllib2.Request('%s/_admin/api' % apihost, + data=json.dumps(_build_data(id_)), + headers={'content-type': 'text/plain'}) + if format == FORMAT_PRETTY: + sys.stdout.write('calling %s to %s \n' % (req.get_data(), apihost)) + ret = urllib2.urlopen(req) + raw_json = ret.read() + json_data = json.loads(raw_json) + id_ret = json_data['id'] + _formatted_json = pprint.pformat(json_data) + if id_ret == id_: + if format == FORMAT_JSON: + sys.stdout.write(str(raw_json)) + else: + sys.stdout.write('rhodecode returned:\n%s\n' % (_formatted_json)) + + else: + raise Exception('something went wrong. ' + 'ID mismatch got %s, expected %s | %s' % ( + id_ret, id_, _formatted_json)) + + +def argparser(argv): + usage = ( + "rhodecode_api [-h] [--format=FORMAT] [--apikey=APIKEY] [--apihost=APIHOST] " + " [--config=CONFIG] " + "_create_config or METHOD ..." + ) + + parser = argparse.ArgumentParser(description='RhodeCode API cli', + usage=usage) + + ## config + group = parser.add_argument_group('config') + group.add_argument('--apikey', help='api access key') + group.add_argument('--apihost', help='api host') + group.add_argument('--config', help='config file') + + group = parser.add_argument_group('API') + group.add_argument('method', metavar='METHOD', type=str, + help='API method name to call followed by key:value attributes', + ) + group.add_argument('--format', dest='format', type=str, + help='output format default: `pretty` can ' + 'be also `%s`' % FORMAT_JSON, + default=FORMAT_PRETTY + ) + args, other = parser.parse_known_args() + return parser, args, other + + +def main(argv=None): + """ + Main execution function for cli + + :param argv: + :type argv: + """ + if argv is None: + argv = sys.argv + + conf = None + parser, args, other = argparser(argv) + + api_credentials_given = (args.apikey and args.apihost) + if args.method == '_create_config': + if not api_credentials_given: + raise parser.error('_create_config requires --apikey and --apihost') + conf = RcConf(config_location=args.config, + autocreate=True, config={'apikey': args.apikey, + 'apihost': args.apihost}) + + if not conf: + conf = RcConf(config_location=args.config, autoload=True) + if not conf: + if not api_credentials_given: + parser.error('Could not find config file and missing ' + '--apikey or --apihost in params') + + apikey = args.apikey or conf['apikey'] + host = args.apihost or conf['apihost'] + method = args.method + if method == '_create_config': + sys.exit() + + try: + margs = dict(map(lambda s: s.split(':', 1), other)) + except: + sys.stderr.write('Error parsing arguments \n') + sys.exit() + + api_call(apikey, host, args.format, method, **margs) + return 0 + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/rhodecode/lib/backup_manager.py b/rhodecode/bin/rhodecode_backup.py old mode 100644 new mode 100755 rename from rhodecode/lib/backup_manager.py rename to rhodecode/bin/rhodecode_backup.py --- a/rhodecode/lib/backup_manager.py +++ b/rhodecode/bin/rhodecode_backup.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """ - rhodecode.lib.backup_manager + rhodecode.bin.backup_manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Mercurial repositories backup manager, it allows to backups all + Repositories backup manager, it allows to backups all repositories and send it to backup server using RSA key via ssh. :created_on: Feb 28, 2010 @@ -39,7 +39,7 @@ logging.basicConfig(level=logging.DEBUG, class BackupManager(object): def __init__(self, repos_location, rsa_key, backup_server): today = datetime.datetime.now().weekday() + 1 - self.backup_file_name = "mercurial_repos.%s.tar.gz" % today + self.backup_file_name = "rhodecode_repos.%s.tar.gz" % today self.id_rsa_path = self.get_id_rsa(rsa_key) self.repos_path = self.get_repos_path(repos_location) diff --git a/rhodecode/config/deployment.ini_tmpl b/rhodecode/config/deployment.ini_tmpl --- a/rhodecode/config/deployment.ini_tmpl +++ b/rhodecode/config/deployment.ini_tmpl @@ -30,22 +30,31 @@ pdebug = false [server:main] ##nr of threads to spawn -threadpool_workers = 5 +#threadpool_workers = 5 ##max request before thread respawn -threadpool_max_requests = 10 +#threadpool_max_requests = 10 ##option to use threads of process -use_threadpool = true +#use_threadpool = true -use = egg:Paste#http +#use = egg:Paste#http +use = egg:waitress#main host = 127.0.0.1 port = 5000 +[filter:proxy-prefix] +# prefix middleware for rc +use = egg:PasteDeploy#prefix +prefix = / + [app:main] use = egg:rhodecode +#filter-with = proxy-prefix full_stack = true static_files = true +# Optional Languages +# en, fr, ja, pt_BR, zh_CN, zh_TW lang = en cache_dir = %(here)s/data index_dir = %(here)s/data/index @@ -54,6 +63,15 @@ cut_off_limit = 256000 force_https = false commit_parse_limit = 50 use_gravatar = true + +## alternative_gravatar_url allows you to use your own avatar server application +## the following parts of the URL will be replaced +## {email} user email +## {md5email} md5 hash of the user email (like at gravatar.com) +## {size} size of the image that is expected from the server application +#alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size} +#alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size} + container_auth_enabled = false proxypass_auth_enabled = false default_encoding = utf8 @@ -78,7 +96,8 @@ default_encoding = utf8 issue_pat = (?:\s*#)(\d+) ## server url to the issue, each {id} will be replaced with match -## fetched from the regex and {repo} is replaced with repository name +## fetched from the regex and {repo} is replaced with full repository name +## including groups {repo_name} is replaced with just name of repo issue_server_link = https://myissueserver.com/{repo}/issue/{id} @@ -165,30 +184,34 @@ beaker.cache.sql_cache_long.key_length = ## The storage uses the Container API ## that is also used by the cache system. -## db session example - +## db session ## #beaker.session.type = ext:database #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode #beaker.session.table_name = db_session -## encrypted cookie session, good for many instances +## encrypted cookie client side session, good for many instances ## #beaker.session.type = cookie -beaker.session.type = file +## file based cookies (default) ## +#beaker.session.type = file + + beaker.session.key = rhodecode -# secure cookie requires AES python libraries -#beaker.session.encrypt_key = ${app_instance_secret} -#beaker.session.validate_key = ${app_instance_secret} -beaker.session.timeout = 36000 +## secure cookie requires AES python libraries ## +#beaker.session.encrypt_key = g654dcno0-9873jhgfreyu +#beaker.session.validate_key = 9712sds2212c--zxc123 +## sets session as invalid if it haven't been accessed for given amount of time +beaker.session.timeout = 2592000 beaker.session.httponly = true +#beaker.session.cookie_path = / -## uncomment for https secure cookie +## uncomment for https secure cookie ## beaker.session.secure = false -##auto save the session to not to use .save() +## auto save the session to not to use .save() ## beaker.session.auto = False -##true exire at browser close +## default cookie expiration time in seconds `true` expire at browser close ## #beaker.session.cookie_expires = 3600 diff --git a/rhodecode/config/environment.py b/rhodecode/config/environment.py --- a/rhodecode/config/environment.py +++ b/rhodecode/config/environment.py @@ -72,19 +72,28 @@ def load_environment(global_conf, app_co config['pylons.strict_tmpl_context'] = True test = os.path.split(config['__file__'])[-1] == 'test.ini' if test: + if os.environ.get('TEST_DB'): + # swap config if we pass enviroment variable + config['sqlalchemy.db1.url'] = os.environ.get('TEST_DB') + from rhodecode.lib.utils import create_test_env, create_test_index from rhodecode.tests import TESTS_TMP_PATH - create_test_env(TESTS_TMP_PATH, config) - create_test_index(TESTS_TMP_PATH, config, True) + # set RC_NO_TMP_PATH=1 to disable re-creating the database and + # test repos + if not int(os.environ.get('RC_NO_TMP_PATH', 0)): + create_test_env(TESTS_TMP_PATH, config) + # set RC_WHOOSH_TEST_DISABLE=1 to disable whoosh index during tests + if not int(os.environ.get('RC_WHOOSH_TEST_DISABLE', 0)): + create_test_index(TESTS_TMP_PATH, config, True) # MULTIPLE DB configs # Setup the SQLAlchemy database engine sa_engine_db1 = engine_from_config(config, 'sqlalchemy.db1.') - init_model(sa_engine_db1) repos_path = make_ui('db').configitems('paths')[0][1] - repo2db_mapper(ScmModel().repo_scan(repos_path)) + repo2db_mapper(ScmModel().repo_scan(repos_path), + remove_obsolete=False, install_git_hook=False) set_available_permissions(config) config['base_path'] = repos_path set_rhodecode_config(config) diff --git a/rhodecode/config/post_receive_tmpl.py b/rhodecode/config/post_receive_tmpl.py new file mode 100644 --- /dev/null +++ b/rhodecode/config/post_receive_tmpl.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +import os +import sys + +try: + import rhodecode + RC_HOOK_VER = '_TMPL_' + os.environ['RC_HOOK_VER'] = RC_HOOK_VER + from rhodecode.lib.hooks import handle_git_post_receive +except ImportError: + rhodecode = None + + +def main(): + if rhodecode is None: + # exit with success if we cannot import rhodecode !! + # this allows simply push to this repo even without + # rhodecode + sys.exit(0) + + repo_path = os.path.abspath('.') + push_data = sys.stdin.readlines() + # os.environ is modified here by a subprocess call that + # runs git and later git executes this hook. + # Environ get's some additional info from rhodecode system + # like IP or username from basic-auth + handle_git_post_receive(repo_path, push_data, os.environ) + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/rhodecode/config/pre_receive_tmpl.py b/rhodecode/config/pre_receive_tmpl.py new file mode 100644 --- /dev/null +++ b/rhodecode/config/pre_receive_tmpl.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python +import os +import sys + +try: + import rhodecode + RC_HOOK_VER = '_TMPL_' + os.environ['RC_HOOK_VER'] = RC_HOOK_VER + from rhodecode.lib.hooks import handle_git_pre_receive +except ImportError: + rhodecode = None + + +def main(): + if rhodecode is None: + # exit with success if we cannot import rhodecode !! + # this allows simply push to this repo even without + # rhodecode + sys.exit(0) + + repo_path = os.path.abspath('.') + push_data = sys.stdin.readlines() + # os.environ is modified here by a subprocess call that + # runs git and later git executes this hook. + # Environ get's some additional info from rhodecode system + # like IP or username from basic-auth + handle_git_pre_receive(repo_path, push_data, os.environ) + sys.exit(0) + +if __name__ == '__main__': + main() diff --git a/rhodecode/config/rcextensions/__init__.py b/rhodecode/config/rcextensions/__init__.py --- a/rhodecode/config/rcextensions/__init__.py +++ b/rhodecode/config/rcextensions/__init__.py @@ -1,6 +1,7 @@ # Additional mappings that are not present in the pygments lexers # used for building stats -# format is {'ext':'Name'} eg. {'py':'Python'} +# format is {'ext':['Names']} eg. {'py':['Python']} note: there can be +# more than one name for extension # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP # build by pygments EXTRA_MAPPINGS = {} @@ -39,6 +40,7 @@ def _crhook(*args, **kwargs): :param group_id: :param created_by: """ + return 0 CREATE_REPO_HOOK = _crhook diff --git a/rhodecode/config/rcextensions/make_rcextensions.py b/rhodecode/config/rcextensions/make_rcextensions.py --- a/rhodecode/config/rcextensions/make_rcextensions.py +++ b/rhodecode/config/rcextensions/make_rcextensions.py @@ -54,7 +54,7 @@ class MakeRcExt(BasePasterCommand): logging.config.fileConfig(self.path_to_ini_file) from pylons import config - def _make_file(ext_file): + def _make_file(ext_file, tmpl): bdir = os.path.split(ext_file)[0] if not os.path.isdir(bdir): os.makedirs(bdir) @@ -71,11 +71,11 @@ class MakeRcExt(BasePasterCommand): msg = ('Extension file already exists, do you want ' 'to overwrite it ? [y/n]') if ask_ok(msg): - _make_file(ext_file) + _make_file(ext_file, tmpl) else: log.info('nothing done...') else: - _make_file(ext_file) + _make_file(ext_file, tmpl) def update_parser(self): pass diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py --- a/rhodecode/config/routing.py +++ b/rhodecode/config/routing.py @@ -69,7 +69,7 @@ def make_map(config): rmap.connect('home', '/', controller='home', action='index') rmap.connect('repo_switcher', '/repos', controller='home', action='repo_switcher') - rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*}', + rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}', controller='home', action='branch_tag_switcher') rmap.connect('bugtracker', "http://bitbucket.org/marcinkuzminski/rhodecode/issues", @@ -93,52 +93,54 @@ def make_map(config): action="new", conditions=dict(method=["GET"])) m.connect("formatted_new_repo", "/repos/new.{format}", action="new", conditions=dict(method=["GET"])) - m.connect("/repos/{repo_name:.*}", + m.connect("/repos/{repo_name:.*?}", action="update", conditions=dict(method=["PUT"], function=check_repo)) - m.connect("/repos/{repo_name:.*}", + m.connect("/repos/{repo_name:.*?}", action="delete", conditions=dict(method=["DELETE"], function=check_repo)) - m.connect("edit_repo", "/repos/{repo_name:.*}/edit", + m.connect("edit_repo", "/repos/{repo_name:.*?}/edit", action="edit", conditions=dict(method=["GET"], function=check_repo)) - m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit", + m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit", action="edit", conditions=dict(method=["GET"], function=check_repo)) - m.connect("repo", "/repos/{repo_name:.*}", + m.connect("repo", "/repos/{repo_name:.*?}", action="show", conditions=dict(method=["GET"], function=check_repo)) - m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}", + m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}", action="show", conditions=dict(method=["GET"], function=check_repo)) #ajax delete repo perm user - m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}", + m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}", action="delete_perm_user", conditions=dict(method=["DELETE"], function=check_repo)) #ajax delete repo perm users_group m.connect('delete_repo_users_group', - "/repos_delete_users_group/{repo_name:.*}", + "/repos_delete_users_group/{repo_name:.*?}", action="delete_perm_users_group", conditions=dict(method=["DELETE"], function=check_repo)) #settings actions - m.connect('repo_stats', "/repos_stats/{repo_name:.*}", + m.connect('repo_stats', "/repos_stats/{repo_name:.*?}", action="repo_stats", conditions=dict(method=["DELETE"], function=check_repo)) - m.connect('repo_cache', "/repos_cache/{repo_name:.*}", + m.connect('repo_cache', "/repos_cache/{repo_name:.*?}", action="repo_cache", conditions=dict(method=["DELETE"], function=check_repo)) - m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}", + m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}", action="repo_public_journal", conditions=dict(method=["PUT"], function=check_repo)) - m.connect('repo_pull', "/repo_pull/{repo_name:.*}", + m.connect('repo_pull', "/repo_pull/{repo_name:.*?}", action="repo_pull", conditions=dict(method=["PUT"], function=check_repo)) - m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*}", + m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}", action="repo_as_fork", conditions=dict(method=["PUT"], function=check_repo)) - + m.connect('repo_locking', "/repo_locking/{repo_name:.*?}", + action="repo_locking", conditions=dict(method=["PUT"], + function=check_repo)) with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/repos_groups') as m: m.connect("repos_groups", "/repos_groups", @@ -157,9 +159,8 @@ def make_map(config): m.connect("delete_repos_group", "/repos_groups/{id}", action="delete", conditions=dict(method=["DELETE"], function=check_int)) - m.connect("edit_repos_group", "/repos_groups/{id}/edit", - action="edit", conditions=dict(method=["GET"], - function=check_int)) + m.connect("edit_repos_group", "/repos_groups/{id:.*?}/edit", + action="edit", conditions=dict(method=["GET"],)) m.connect("formatted_edit_repos_group", "/repos_groups/{id}.{format}/edit", action="edit", conditions=dict(method=["GET"], @@ -212,8 +213,12 @@ def make_map(config): #EXTRAS USER ROUTES m.connect("user_perm", "/users_perm/{id}", action="update_perm", conditions=dict(method=["PUT"])) + m.connect("user_emails", "/users_emails/{id}", + action="add_email", conditions=dict(method=["PUT"])) + m.connect("user_emails_delete", "/users_emails/{id}", + action="delete_email", conditions=dict(method=["DELETE"])) - #ADMIN USERS REST ROUTES + #ADMIN USERS GROUPS REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, controller='admin/users_groups') as m: m.connect("users_groups", "/users_groups", @@ -292,6 +297,10 @@ def make_map(config): action="my_account_update", conditions=dict(method=["PUT"])) m.connect("admin_settings_create_repository", "/create_repository", action="create_repository", conditions=dict(method=["GET"])) + m.connect("admin_settings_my_repos", "/my_account/repos", + action="my_account_my_repos", conditions=dict(method=["GET"])) + m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests", + action="my_account_my_pullrequests", conditions=dict(method=["GET"])) #NOTIFICATION REST ROUTES with rmap.submapper(path_prefix=ADMIN_PREFIX, @@ -337,15 +346,27 @@ def make_map(config): m.connect('api', '/api') #USER JOURNAL - rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal') + rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, + controller='journal', action='index') + rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX, + controller='journal', action='journal_rss') + rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX, + controller='journal', action='journal_atom') rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX, controller='journal', action="public_journal") - rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX, + rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX, + controller='journal', action="public_journal_rss") + + rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX, controller='journal', action="public_journal_rss") rmap.connect('public_journal_atom', + '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal', + action="public_journal_atom") + + rmap.connect('public_journal_atom_old', '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal', action="public_journal_atom") @@ -374,18 +395,18 @@ def make_map(config): controller='login', action='password_reset_confirmation') #FEEDS - rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss', + rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss', controller='feed', action='rss', conditions=dict(function=check_repo)) - rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom', + rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom', controller='feed', action='atom', conditions=dict(function=check_repo)) #========================================================================== # REPOSITORY ROUTES #========================================================================== - rmap.connect('summary_home', '/{repo_name:.*}', + rmap.connect('summary_home', '/{repo_name:.*?}', controller='summary', conditions=dict(function=check_repo)) @@ -393,114 +414,166 @@ def make_map(config): controller='admin/repos_groups', action="show_by_name", conditions=dict(function=check_group)) - rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}', + rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}', controller='changeset', revision='tip', conditions=dict(function=check_repo)) rmap.connect('changeset_comment', - '/{repo_name:.*}/changeset/{revision}/comment', + '/{repo_name:.*?}/changeset/{revision}/comment', controller='changeset', revision='tip', action='comment', conditions=dict(function=check_repo)) rmap.connect('changeset_comment_delete', - '/{repo_name:.*}/changeset/comment/{comment_id}/delete', + '/{repo_name:.*?}/changeset/comment/{comment_id}/delete', controller='changeset', action='delete_comment', conditions=dict(function=check_repo, method=["DELETE"])) rmap.connect('raw_changeset_home', - '/{repo_name:.*}/raw-changeset/{revision}', + '/{repo_name:.*?}/raw-changeset/{revision}', controller='changeset', action='raw_changeset', revision='tip', conditions=dict(function=check_repo)) - rmap.connect('summary_home', '/{repo_name:.*}/summary', + rmap.connect('compare_url', + '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}', + controller='compare', action='index', + conditions=dict(function=check_repo), + requirements=dict( + org_ref_type='(branch|book|tag|rev|org_ref_type)', + other_ref_type='(branch|book|tag|rev|other_ref_type)') + ) + + rmap.connect('pullrequest_home', + '/{repo_name:.*?}/pull-request/new', controller='pullrequests', + action='index', conditions=dict(function=check_repo, + method=["GET"])) + + rmap.connect('pullrequest', + '/{repo_name:.*?}/pull-request/new', controller='pullrequests', + action='create', conditions=dict(function=check_repo, + method=["POST"])) + + rmap.connect('pullrequest_show', + '/{repo_name:.*?}/pull-request/{pull_request_id}', + controller='pullrequests', + action='show', conditions=dict(function=check_repo, + method=["GET"])) + rmap.connect('pullrequest_update', + '/{repo_name:.*?}/pull-request/{pull_request_id}', + controller='pullrequests', + action='update', conditions=dict(function=check_repo, + method=["PUT"])) + rmap.connect('pullrequest_delete', + '/{repo_name:.*?}/pull-request/{pull_request_id}', + controller='pullrequests', + action='delete', conditions=dict(function=check_repo, + method=["DELETE"])) + + rmap.connect('pullrequest_show_all', + '/{repo_name:.*?}/pull-request', + controller='pullrequests', + action='show_all', conditions=dict(function=check_repo, + method=["GET"])) + + rmap.connect('pullrequest_comment', + '/{repo_name:.*?}/pull-request-comment/{pull_request_id}', + controller='pullrequests', + action='comment', conditions=dict(function=check_repo, + method=["POST"])) + + rmap.connect('pullrequest_comment_delete', + '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete', + controller='pullrequests', action='delete_comment', + conditions=dict(function=check_repo, method=["DELETE"])) + + rmap.connect('summary_home', '/{repo_name:.*?}/summary', controller='summary', conditions=dict(function=check_repo)) - rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog', + rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog', controller='shortlog', conditions=dict(function=check_repo)) - rmap.connect('branches_home', '/{repo_name:.*}/branches', + rmap.connect('branches_home', '/{repo_name:.*?}/branches', controller='branches', conditions=dict(function=check_repo)) - rmap.connect('tags_home', '/{repo_name:.*}/tags', + rmap.connect('tags_home', '/{repo_name:.*?}/tags', controller='tags', conditions=dict(function=check_repo)) - rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks', + rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks', controller='bookmarks', conditions=dict(function=check_repo)) - rmap.connect('changelog_home', '/{repo_name:.*}/changelog', + rmap.connect('changelog_home', '/{repo_name:.*?}/changelog', controller='changelog', conditions=dict(function=check_repo)) - rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}', + rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}', controller='changelog', action='changelog_details', conditions=dict(function=check_repo)) - rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}', + rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}', controller='files', revision='tip', f_path='', conditions=dict(function=check_repo)) - rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}', + rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}', controller='files', action='diff', revision='tip', f_path='', conditions=dict(function=check_repo)) rmap.connect('files_rawfile_home', - '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}', + '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}', controller='files', action='rawfile', revision='tip', f_path='', conditions=dict(function=check_repo)) rmap.connect('files_raw_home', - '/{repo_name:.*}/raw/{revision}/{f_path:.*}', + '/{repo_name:.*?}/raw/{revision}/{f_path:.*}', controller='files', action='raw', revision='tip', f_path='', conditions=dict(function=check_repo)) rmap.connect('files_annotate_home', - '/{repo_name:.*}/annotate/{revision}/{f_path:.*}', + '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}', controller='files', action='index', revision='tip', f_path='', annotate=True, conditions=dict(function=check_repo)) rmap.connect('files_edit_home', - '/{repo_name:.*}/edit/{revision}/{f_path:.*}', + '/{repo_name:.*?}/edit/{revision}/{f_path:.*}', controller='files', action='edit', revision='tip', f_path='', conditions=dict(function=check_repo)) rmap.connect('files_add_home', - '/{repo_name:.*}/add/{revision}/{f_path:.*}', + '/{repo_name:.*?}/add/{revision}/{f_path:.*}', controller='files', action='add', revision='tip', f_path='', conditions=dict(function=check_repo)) - rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}', + rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}', controller='files', action='archivefile', conditions=dict(function=check_repo)) rmap.connect('files_nodelist_home', - '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}', + '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}', controller='files', action='nodelist', conditions=dict(function=check_repo)) - rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings', + rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings', controller='settings', action="delete", conditions=dict(method=["DELETE"], function=check_repo)) - rmap.connect('repo_settings_update', '/{repo_name:.*}/settings', + rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings', controller='settings', action="update", conditions=dict(method=["PUT"], function=check_repo)) - rmap.connect('repo_settings_home', '/{repo_name:.*}/settings', + rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings', controller='settings', action='index', conditions=dict(function=check_repo)) - rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork', + rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork', controller='forks', action='fork_create', conditions=dict(function=check_repo, method=["POST"])) - rmap.connect('repo_fork_home', '/{repo_name:.*}/fork', + rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork', controller='forks', action='fork', conditions=dict(function=check_repo)) - rmap.connect('repo_forks_home', '/{repo_name:.*}/forks', + rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks', controller='forks', action='forks', conditions=dict(function=check_repo)) - rmap.connect('repo_followers_home', '/{repo_name:.*}/followers', + rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers', controller='followers', action='followers', conditions=dict(function=check_repo)) diff --git a/rhodecode/controllers/admin/admin.py b/rhodecode/controllers/admin/admin.py --- a/rhodecode/controllers/admin/admin.py +++ b/rhodecode/controllers/admin/admin.py @@ -45,7 +45,7 @@ class AdminController(BaseController): @HasPermissionAllDecorator('hg.admin') def index(self): - users_log = self.sa.query(UserLog)\ + users_log = UserLog.query()\ .options(joinedload(UserLog.user))\ .options(joinedload(UserLog.repository))\ .order_by(UserLog.action_date.desc()) diff --git a/rhodecode/controllers/admin/ldap_settings.py b/rhodecode/controllers/admin/ldap_settings.py --- a/rhodecode/controllers/admin/ldap_settings.py +++ b/rhodecode/controllers/admin/ldap_settings.py @@ -40,6 +40,7 @@ from rhodecode.lib.auth import LoginRequ from rhodecode.lib.exceptions import LdapImportError from rhodecode.model.forms import LdapSettingsForm from rhodecode.model.db import RhodeCodeSetting +from rhodecode.model.meta import Session log = logging.getLogger(__name__) @@ -119,9 +120,9 @@ class LdapSettingsController(BaseControl v = ldap_active setting = RhodeCodeSetting.get_by_name(k) setting.app_settings_value = v - self.sa.add(setting) + Session().add(setting) - self.sa.commit() + Session().commit() h.flash(_('Ldap settings updated successfully'), category='success') if not ldap_active: diff --git a/rhodecode/controllers/admin/notifications.py b/rhodecode/controllers/admin/notifications.py --- a/rhodecode/controllers/admin/notifications.py +++ b/rhodecode/controllers/admin/notifications.py @@ -60,19 +60,33 @@ class NotificationsController(BaseContro """GET /_admin/notifications: All items in the collection""" # url('notifications') c.user = self.rhodecode_user - notif = NotificationModel().get_for_user(self.rhodecode_user.user_id) + notif = NotificationModel().get_for_user(self.rhodecode_user.user_id, + filter_=request.GET.getall('type')) p = int(request.params.get('page', 1)) c.notifications = Page(notif, page=p, items_per_page=10) + c.pull_request_type = Notification.TYPE_PULL_REQUEST + c.comment_type = [Notification.TYPE_CHANGESET_COMMENT, + Notification.TYPE_PULL_REQUEST_COMMENT] + + _current_filter = request.GET.getall('type') + c.current_filter = 'all' + if _current_filter == [c.pull_request_type]: + c.current_filter = 'pull_request' + elif _current_filter == c.comment_type: + c.current_filter = 'comment' + return render('admin/notifications/notifications.html') def mark_all_read(self): if request.environ.get('HTTP_X_PARTIAL_XHR'): nm = NotificationModel() # mark all read - nm.mark_all_read_for_user(self.rhodecode_user.user_id) - Session.commit() + nm.mark_all_read_for_user(self.rhodecode_user.user_id, + filter_=request.GET.getall('type')) + Session().commit() c.user = self.rhodecode_user - notif = nm.get_for_user(self.rhodecode_user.user_id) + notif = nm.get_for_user(self.rhodecode_user.user_id, + filter_=request.GET.getall('type')) c.notifications = Page(notif, page=1, items_per_page=10) return render('admin/notifications/notifications_data.html') @@ -92,6 +106,18 @@ class NotificationsController(BaseContro # h.form(url('notification', notification_id=ID), # method='put') # 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) + if h.HasPermissionAny('hg.admin')() or owner: + NotificationModel().mark_read(c.rhodecode_user.user_id, no) + Session().commit() + return 'ok' + except Exception: + Session.rollback() + log.error(traceback.format_exc()) + return 'fail' def delete(self, notification_id): """DELETE /_admin/notifications/id: Delete an existing item""" @@ -106,9 +132,9 @@ class NotificationsController(BaseContro no = Notification.get(notification_id) owner = lambda: (no.notifications_to_users.user.user_id == c.rhodecode_user.user_id) - if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: + if h.HasPermissionAny('hg.admin')() or owner: NotificationModel().delete(c.rhodecode_user.user_id, no) - Session.commit() + Session().commit() return 'ok' except Exception: Session.rollback() @@ -132,7 +158,7 @@ class NotificationsController(BaseContro if unotification: if unotification.read is False: unotification.mark_as_read() - Session.commit() + Session().commit() c.notification = no return render('admin/notifications/show_notification.html') diff --git a/rhodecode/controllers/admin/permissions.py b/rhodecode/controllers/admin/permissions.py --- a/rhodecode/controllers/admin/permissions.py +++ b/rhodecode/controllers/admin/permissions.py @@ -71,6 +71,15 @@ class PermissionsController(BaseControll self.create_choices = [('hg.create.none', _('Disabled')), ('hg.create.repository', _('Enabled'))] + self.fork_choices = [('hg.fork.none', _('Disabled')), + ('hg.fork.repository', _('Enabled'))] + + # set the global template variables + c.perms_choices = self.perms_choices + c.register_choices = self.register_choices + c.create_choices = self.create_choices + c.fork_choices = self.fork_choices + def index(self, format='html'): """GET /permissions: All items in the collection""" # url('permissions') @@ -96,20 +105,18 @@ class PermissionsController(BaseControll _form = DefaultPermissionsForm([x[0] for x in self.perms_choices], [x[0] for x in self.register_choices], - [x[0] for x in self.create_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() + Session().commit() h.flash(_('Default permissions updated successfully'), category='success') except formencode.Invalid, errors: - c.perms_choices = self.perms_choices - c.register_choices = self.register_choices - c.create_choices = self.create_choices defaults = errors.value return htmlfill.render( @@ -141,10 +148,8 @@ class PermissionsController(BaseControll def edit(self, id, format='html'): """GET /permissions/id/edit: Form to edit an existing item""" #url('edit_permission', id=ID) - c.perms_choices = self.perms_choices - c.register_choices = self.register_choices - c.create_choices = self.create_choices + #this form can only edit default user permissions if id == 'default': default_user = User.get_by_username('default') defaults = {'_method': 'put', @@ -160,10 +165,14 @@ class PermissionsController(BaseControll if p.permission.permission_name.startswith('hg.create.'): defaults['default_create'] = p.permission.permission_name + if p.permission.permission_name.startswith('hg.fork.'): + defaults['default_fork'] = p.permission.permission_name + return htmlfill.render( - render('admin/permissions/permissions.html'), - defaults=defaults, - encoding="UTF-8", - force_defaults=True,) + render('admin/permissions/permissions.html'), + defaults=defaults, + encoding="UTF-8", + force_defaults=True, + ) else: return redirect(url('admin_home')) diff --git a/rhodecode/controllers/admin/repos.py b/rhodecode/controllers/admin/repos.py --- a/rhodecode/controllers/admin/repos.py +++ b/rhodecode/controllers/admin/repos.py @@ -28,12 +28,13 @@ import traceback import formencode from formencode import htmlfill -from paste.httpexceptions import HTTPInternalServerError +from webob.exc import HTTPInternalServerError from pylons import request, session, tmpl_context as c, url from pylons.controllers.util import redirect from pylons.i18n.translation import _ from sqlalchemy.exc import IntegrityError +import rhodecode from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \ HasPermissionAnyDecorator, HasRepoPermissionAllDecorator @@ -45,6 +46,7 @@ from rhodecode.model.db import User, Rep from rhodecode.model.forms import RepoForm from rhodecode.model.scm import ScmModel from rhodecode.model.repo import RepoModel +from rhodecode.lib.compat import json log = logging.getLogger(__name__) @@ -70,6 +72,8 @@ class ReposController(BaseController): repo_model = RepoModel() c.users_array = repo_model.get_users_js() c.users_groups_array = repo_model.get_users_groups_js() + choices, c.landing_revs = ScmModel().get_repo_landing_revs() + c.landing_revs_choices = choices def __load_data(self, repo_name=None): """ @@ -91,6 +95,9 @@ class ReposController(BaseController): return redirect(url('repos')) + choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info) + c.landing_revs_choices = choices + c.default_user_id = User.get_by_username('default').user_id c.in_public_journal = UserFollowing.query()\ .filter(UserFollowing.user_id == c.default_user_id)\ @@ -115,7 +122,10 @@ class ReposController(BaseController): c.repos_list = [('', _('--REMOVE FORK--'))] c.repos_list += [(x.repo_id, x.repo_name) for x in - Repository.query().order_by(Repository.repo_name).all()] + Repository.query().order_by(Repository.repo_name).all() + if x.repo_id != c.repo_info.repo_id] + + defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else '' return defaults @HasPermissionAllDecorator('hg.admin') @@ -123,9 +133,45 @@ class ReposController(BaseController): """GET /repos: All items in the collection""" # url('repos') - c.repos_list = ScmModel().get_repos(Repository.query() - .order_by(Repository.repo_name) - .all(), sort_key='name_sort') + c.repos_list = Repository.query()\ + .order_by(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, + "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 + }) + return render('admin/repos/repos.html') @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') @@ -137,9 +183,11 @@ class ReposController(BaseController): self.__load_defaults() form_result = {} try: - form_result = RepoForm(repo_groups=c.repo_groups_choices)()\ + form_result = RepoForm(repo_groups=c.repo_groups_choices, + landing_revs=c.landing_revs_choices)()\ .to_python(dict(request.POST)) - RepoModel().create(form_result, self.rhodecode_user) + new_repo = RepoModel().create(form_result, + self.rhodecode_user.user_id) if form_result['clone_uri']: h.flash(_('created repository %s from %s') \ % (form_result['repo_name'], form_result['clone_uri']), @@ -151,11 +199,13 @@ class ReposController(BaseController): if request.POST.get('user_created'): # created by regular non admin user action_logger(self.rhodecode_user, 'user_created_repo', - form_result['repo_name_full'], '', self.sa) + form_result['repo_name_full'], self.ip_addr, + self.sa) else: action_logger(self.rhodecode_user, 'admin_created_repo', - form_result['repo_name_full'], '', self.sa) - Session.commit() + form_result['repo_name_full'], self.ip_addr, + self.sa) + Session().commit() except formencode.Invalid, errors: c.new_repo = errors.value['repo_name'] @@ -177,9 +227,9 @@ class ReposController(BaseController): msg = _('error occurred during creation of repository %s') \ % form_result.get('repo_name') h.flash(msg, category='error') - if request.POST.get('user_created'): - return redirect(url('home')) - return redirect(url('repos')) + return redirect(url('repos')) + #redirect to our new repo ! + return redirect(url('summary_home', repo_name=new_repo.repo_name)) @HasPermissionAllDecorator('hg.admin') def new(self, format='html'): @@ -202,18 +252,23 @@ class ReposController(BaseController): self.__load_defaults() repo_model = RepoModel() changed_name = repo_name + #override the choices with extracted revisions ! + choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name) + c.landing_revs_choices = choices + _form = RepoForm(edit=True, old_data={'repo_name': repo_name}, - repo_groups=c.repo_groups_choices)() + repo_groups=c.repo_groups_choices, + landing_revs=c.landing_revs_choices)() try: form_result = _form.to_python(dict(request.POST)) repo = repo_model.update(repo_name, form_result) invalidate_cache('get_repo_cached_%s' % repo_name) - h.flash(_('Repository %s updated successfully' % repo_name), + h.flash(_('Repository %s updated successfully') % repo_name, category='success') changed_name = repo.repo_name action_logger(self.rhodecode_user, 'admin_updated_repo', - changed_name, '', self.sa) - Session.commit() + changed_name, self.ip_addr, self.sa) + Session().commit() except formencode.Invalid, errors: defaults = self.__load_data(repo_name) defaults.update(errors.value) @@ -253,11 +308,11 @@ class ReposController(BaseController): return redirect(url('repos')) try: action_logger(self.rhodecode_user, 'admin_deleted_repo', - repo_name, '', self.sa) + repo_name, self.ip_addr, self.sa) repo_model.delete(repo) invalidate_cache('get_repo_cached_%s' % repo_name) h.flash(_('deleted repository %s') % repo_name, category='success') - Session.commit() + Session().commit() except IntegrityError, e: if e.message.find('repositories_fork_id_fkey') != -1: log.error(traceback.format_exc()) @@ -287,7 +342,7 @@ class ReposController(BaseController): try: RepoModel().revoke_user_permission(repo=repo_name, user=request.POST['user_id']) - Session.commit() + Session().commit() except Exception: log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of repository user'), @@ -306,7 +361,7 @@ class ReposController(BaseController): RepoModel().revoke_users_group_permission( repo=repo_name, group_name=request.POST['users_group_id'] ) - Session.commit() + Session().commit() except Exception: log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of repository' @@ -324,8 +379,9 @@ class ReposController(BaseController): try: RepoModel().delete_stats(repo_name) - Session.commit() + Session().commit() except Exception, e: + log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of repository stats'), category='error') return redirect(url('edit_repo', repo_name=repo_name)) @@ -340,13 +396,34 @@ class ReposController(BaseController): try: ScmModel().mark_for_invalidation(repo_name) - Session.commit() + Session().commit() except Exception, e: + log.error(traceback.format_exc()) h.flash(_('An error occurred during cache invalidation'), category='error') return redirect(url('edit_repo', repo_name=repo_name)) @HasPermissionAllDecorator('hg.admin') + def repo_locking(self, repo_name): + """ + Unlock repository when it is locked ! + + :param repo_name: + """ + + try: + repo = Repository.get_by_repo_name(repo_name) + if request.POST.get('set_lock'): + Repository.lock(repo, c.rhodecode_user.user_id) + elif request.POST.get('set_unlock'): + Repository.unlock(repo) + except Exception, e: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during unlocking'), + category='error') + return redirect(url('edit_repo', repo_name=repo_name)) + + @HasPermissionAllDecorator('hg.admin') def repo_public_journal(self, repo_name): """ Set's this repository to be visible in public journal, @@ -364,7 +441,7 @@ class ReposController(BaseController): self.scm_model.toggle_following_repo(repo_id, user_id) h.flash(_('Updated repository visibility in public journal'), category='success') - Session.commit() + Session().commit() except: h.flash(_('An error occurred during setting this' ' repository in public journal'), @@ -403,11 +480,11 @@ class ReposController(BaseController): repo = ScmModel().mark_as_fork(repo_name, fork_id, self.rhodecode_user.username) fork = repo.fork.repo_name if repo.fork else _('Nothing') - Session.commit() - h.flash(_('Marked repo %s as fork of %s' % (repo_name,fork)), + Session().commit() + h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork), category='success') except Exception, e: - raise + log.error(traceback.format_exc()) h.flash(_('An error occurred during this operation'), category='error') diff --git a/rhodecode/controllers/admin/repos_groups.py b/rhodecode/controllers/admin/repos_groups.py --- a/rhodecode/controllers/admin/repos_groups.py +++ b/rhodecode/controllers/admin/repos_groups.py @@ -44,7 +44,7 @@ from rhodecode.model.repos_group import from rhodecode.model.forms import ReposGroupForm from rhodecode.model.meta import Session from rhodecode.model.repo import RepoModel -from webob.exc import HTTPInternalServerError +from webob.exc import HTTPInternalServerError, HTTPNotFound log = logging.getLogger(__name__) @@ -74,11 +74,8 @@ class ReposGroupsController(BaseControll :param group_id: """ self.__load_defaults() - - repo_group = RepoGroup.get(group_id) - + repo_group = RepoGroup.get_or_404(group_id) data = repo_group.get_dict() - data['group_name'] = repo_group.name # fill repository users @@ -115,7 +112,7 @@ class ReposGroupsController(BaseControll group_description=form_result['group_description'], parent=form_result['group_parent_id'] ) - Session.commit() + Session().commit() h.flash(_('created repos group %s') \ % form_result['group_name'], category='success') #TODO: in futureaction_logger(, '', '', '', self.sa) @@ -162,7 +159,7 @@ class ReposGroupsController(BaseControll try: form_result = repos_group_form.to_python(dict(request.POST)) ReposGroupModel().update(id, form_result) - Session.commit() + Session().commit() h.flash(_('updated repos group %s') \ % form_result['group_name'], category='success') #TODO: in futureaction_logger(, '', '', '', self.sa) @@ -179,7 +176,7 @@ class ReposGroupsController(BaseControll h.flash(_('error occurred during update of repos group %s') \ % request.POST.get('group_name'), category='error') - return redirect(url('repos_groups')) + return redirect(url('edit_repos_group', id=id)) @HasPermissionAnyDecorator('hg.admin') def delete(self, id): @@ -195,17 +192,18 @@ class ReposGroupsController(BaseControll repos = gr.repositories.all() if repos: h.flash(_('This group contains %s repositores and cannot be ' - 'deleted' % len(repos)), + 'deleted') % len(repos), category='error') return redirect(url('repos_groups')) try: ReposGroupModel().delete(id) - Session.commit() - h.flash(_('removed repos group %s' % gr.group_name), category='success') + Session().commit() + h.flash(_('removed repos group %s') % gr.group_name, + category='success') #TODO: in future action_logger(, '', '', '', self.sa) except IntegrityError, e: - if e.message.find('groups_group_parent_id_fkey') != -1: + if str(e.message).find('groups_group_parent_id_fkey') != -1: log.error(traceback.format_exc()) h.flash(_('Cannot delete this group it still contains ' 'subgroups'), @@ -213,12 +211,12 @@ class ReposGroupsController(BaseControll else: log.error(traceback.format_exc()) h.flash(_('error occurred during deletion of repos ' - 'group %s' % gr.group_name), category='error') + 'group %s') % gr.group_name, category='error') except Exception: log.error(traceback.format_exc()) h.flash(_('error occurred during deletion of repos ' - 'group %s' % gr.group_name), category='error') + 'group %s') % gr.group_name, category='error') return redirect(url('repos_groups')) @@ -234,7 +232,7 @@ class ReposGroupsController(BaseControll ReposGroupModel().revoke_user_permission( repos_group=group_name, user=request.POST['user_id'] ) - Session.commit() + Session().commit() except Exception: log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of group user'), @@ -254,7 +252,7 @@ class ReposGroupsController(BaseControll repos_group=group_name, group_name=request.POST['users_group_id'] ) - Session.commit() + Session().commit() except Exception: log.error(traceback.format_exc()) h.flash(_('An error occurred during deletion of group' @@ -268,8 +266,10 @@ class ReposGroupsController(BaseControll the group by id view instead """ group_name = group_name.rstrip('/') - id_ = RepoGroup.get_by_group_name(group_name).group_id - return self.show(id_) + id_ = RepoGroup.get_by_group_name(group_name) + if id_: + return self.show(id_.group_id) + raise HTTPNotFound @HasReposGroupPermissionAnyDecorator('group.read', 'group.write', 'group.admin') @@ -277,12 +277,9 @@ class ReposGroupsController(BaseControll """GET /repos_groups/id: Show a specific item""" # url('repos_group', id=ID) - c.group = RepoGroup.get(id) + c.group = RepoGroup.get_or_404(id) - if c.group: - c.group_repos = c.group.repositories.all() - else: - return redirect(url('home')) + c.group_repos = c.group.repositories.all() #overwrite our cached list with current filter gr_filter = c.group_repos @@ -292,7 +289,7 @@ class ReposGroupsController(BaseControll c.repo_cnt = 0 - c.groups = self.sa.query(RepoGroup).order_by(RepoGroup.group_name)\ + c.groups = RepoGroup.query().order_by(RepoGroup.group_name)\ .filter(RepoGroup.group_parent_id == id).all() return render('admin/repos_groups/repos_groups.html') @@ -302,13 +299,12 @@ class ReposGroupsController(BaseControll """GET /repos_groups/id/edit: Form to edit an existing item""" # url('edit_repos_group', id=ID) - id_ = int(id) - - c.repos_group = RepoGroup.get(id_) - defaults = self.__load_data(id_) + c.repos_group = ReposGroupModel()._get_repos_group(id) + defaults = self.__load_data(c.repos_group.group_id) # we need to exclude this group from the group list for editing - c.repo_groups = filter(lambda x: x[0] != id_, c.repo_groups) + c.repo_groups = filter(lambda x: x[0] != c.repos_group.group_id, + c.repo_groups) return htmlfill.render( render('admin/repos_groups/repos_groups_edit.html'), diff --git a/rhodecode/controllers/admin/settings.py b/rhodecode/controllers/admin/settings.py --- a/rhodecode/controllers/admin/settings.py +++ b/rhodecode/controllers/admin/settings.py @@ -43,9 +43,9 @@ from rhodecode.lib.celerylib import task from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \ set_rhodecode_config, repo_name_slug from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \ - RhodeCodeSetting + RhodeCodeSetting, PullRequest, PullRequestReviewers from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \ - ApplicationUiSettingsForm + ApplicationUiSettingsForm, ApplicationVisualisationForm from rhodecode.model.scm import ScmModel from rhodecode.model.user import UserModel from rhodecode.model.db import User @@ -79,7 +79,7 @@ class SettingsController(BaseController) # url('admin_settings') defaults = RhodeCodeSetting.get_app_settings() - defaults.update(self.get_hg_ui_settings()) + defaults.update(self._get_hg_ui_settings()) return htmlfill.render( render('admin/settings/settings.html'), @@ -107,6 +107,7 @@ class SettingsController(BaseController) # h.form(url('admin_setting', setting_id=ID), # method='put') # url('admin_setting', setting_id=ID) + if setting_id == 'mapping': rm_obsolete = request.POST.get('destroy', False) log.debug('Rescanning directories with destroy=%s' % rm_obsolete) @@ -119,122 +120,160 @@ class SettingsController(BaseController) h.flash(_('Repositories successfully' ' rescanned added: %s,removed: %s') % (added, removed), - category='success') + category='success') if setting_id == 'whoosh': - repo_location = self.get_hg_ui_settings()['paths_root_path'] + repo_location = self._get_hg_ui_settings()['paths_root_path'] full_index = request.POST.get('full_index', False) run_task(tasks.whoosh_index, repo_location, full_index) + h.flash(_('Whoosh reindex task scheduled'), category='success') - h.flash(_('Whoosh reindex task scheduled'), category='success') if setting_id == 'global': application_form = ApplicationSettingsForm()() try: form_result = application_form.to_python(dict(request.POST)) - - try: - hgsettings1 = RhodeCodeSetting.get_by_name('title') - hgsettings1.app_settings_value = \ - form_result['rhodecode_title'] + except formencode.Invalid, errors: + return htmlfill.render( + render('admin/settings/settings.html'), + defaults=errors.value, + errors=errors.error_dict or {}, + prefix_error=False, + encoding="UTF-8" + ) - hgsettings2 = RhodeCodeSetting.get_by_name('realm') - hgsettings2.app_settings_value = \ - form_result['rhodecode_realm'] + try: + sett1 = RhodeCodeSetting.get_by_name_or_create('title') + sett1.app_settings_value = form_result['rhodecode_title'] + Session().add(sett1) - hgsettings3 = RhodeCodeSetting.get_by_name('ga_code') - hgsettings3.app_settings_value = \ - form_result['rhodecode_ga_code'] + sett2 = RhodeCodeSetting.get_by_name_or_create('realm') + sett2.app_settings_value = form_result['rhodecode_realm'] + Session().add(sett2) - self.sa.add(hgsettings1) - self.sa.add(hgsettings2) - self.sa.add(hgsettings3) - self.sa.commit() - set_rhodecode_config(config) - h.flash(_('Updated application settings'), - category='success') + sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code') + sett3.app_settings_value = form_result['rhodecode_ga_code'] + Session().add(sett3) + + Session().commit() + set_rhodecode_config(config) + h.flash(_('Updated application settings'), category='success') - except Exception: - log.error(traceback.format_exc()) - h.flash(_('error occurred during updating ' - 'application settings'), - category='error') + except Exception: + log.error(traceback.format_exc()) + h.flash(_('error occurred during updating ' + 'application settings'), + category='error') - self.sa.rollback() + if setting_id == 'visual': + application_form = ApplicationVisualisationForm()() + try: + form_result = application_form.to_python(dict(request.POST)) except formencode.Invalid, errors: return htmlfill.render( render('admin/settings/settings.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") + encoding="UTF-8" + ) + + try: + sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon') + sett1.app_settings_value = \ + form_result['rhodecode_show_public_icon'] + + sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon') + sett2.app_settings_value = \ + form_result['rhodecode_show_private_icon'] + + sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags') + sett3.app_settings_value = \ + form_result['rhodecode_stylify_metatags'] - if setting_id == 'mercurial': + Session().add(sett1) + Session().add(sett2) + Session().add(sett3) + Session().commit() + set_rhodecode_config(config) + h.flash(_('Updated visualisation settings'), + category='success') + + except Exception: + log.error(traceback.format_exc()) + h.flash(_('error occurred during updating ' + 'visualisation settings'), + category='error') + + if setting_id == 'vcs': application_form = ApplicationUiSettingsForm()() try: form_result = application_form.to_python(dict(request.POST)) - - try: - - hgsettings1 = self.sa.query(RhodeCodeUi)\ - .filter(RhodeCodeUi.ui_key == 'push_ssl').one() - hgsettings1.ui_value = form_result['web_push_ssl'] - - hgsettings2 = self.sa.query(RhodeCodeUi)\ - .filter(RhodeCodeUi.ui_key == '/').one() - hgsettings2.ui_value = form_result['paths_root_path'] - - #HOOKS - hgsettings3 = self.sa.query(RhodeCodeUi)\ - .filter(RhodeCodeUi.ui_key == 'changegroup.update').one() - hgsettings3.ui_active = \ - bool(form_result['hooks_changegroup_update']) - - hgsettings4 = self.sa.query(RhodeCodeUi)\ - .filter(RhodeCodeUi.ui_key == - 'changegroup.repo_size').one() - hgsettings4.ui_active = \ - bool(form_result['hooks_changegroup_repo_size']) - - hgsettings5 = self.sa.query(RhodeCodeUi)\ - .filter(RhodeCodeUi.ui_key == - 'pretxnchangegroup.push_logger').one() - hgsettings5.ui_active = \ - bool(form_result['hooks_pretxnchangegroup' - '_push_logger']) - - hgsettings6 = self.sa.query(RhodeCodeUi)\ - .filter(RhodeCodeUi.ui_key == - 'preoutgoing.pull_logger').one() - hgsettings6.ui_active = \ - bool(form_result['hooks_preoutgoing_pull_logger']) - - self.sa.add(hgsettings1) - self.sa.add(hgsettings2) - self.sa.add(hgsettings3) - self.sa.add(hgsettings4) - self.sa.add(hgsettings5) - self.sa.add(hgsettings6) - self.sa.commit() - - h.flash(_('Updated mercurial settings'), - category='success') - - except: - log.error(traceback.format_exc()) - h.flash(_('error occurred during updating ' - 'application settings'), category='error') - - self.sa.rollback() - except formencode.Invalid, errors: return htmlfill.render( render('admin/settings/settings.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, - encoding="UTF-8") + encoding="UTF-8" + ) + + try: + # fix namespaces for hooks and extensions + _f = lambda s: s.replace('.', '_') + + sett = RhodeCodeUi.get_by_key('push_ssl') + sett.ui_value = form_result['web_push_ssl'] + Session().add(sett) + + sett = RhodeCodeUi.get_by_key('/') + sett.ui_value = form_result['paths_root_path'] + Session().add(sett) + + #HOOKS + sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE) + sett.ui_active = form_result[_f('hooks_%s' % + RhodeCodeUi.HOOK_UPDATE)] + Session().add(sett) + + sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE) + sett.ui_active = form_result[_f('hooks_%s' % + RhodeCodeUi.HOOK_REPO_SIZE)] + Session().add(sett) + + sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH) + sett.ui_active = form_result[_f('hooks_%s' % + RhodeCodeUi.HOOK_PUSH)] + Session().add(sett) + + sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL) + sett.ui_active = form_result[_f('hooks_%s' % + RhodeCodeUi.HOOK_PULL)] + + Session().add(sett) + + ## EXTENSIONS + sett = RhodeCodeUi.get_by_key('largefiles') + sett.ui_active = form_result[_f('extensions_largefiles')] + Session().add(sett) + + sett = RhodeCodeUi.get_by_key('hgsubversion') + sett.ui_active = form_result[_f('extensions_hgsubversion')] + Session().add(sett) + +# sett = RhodeCodeUi.get_by_key('hggit') +# sett.ui_active = form_result[_f('extensions_hggit')] +# Session().add(sett) + + Session().commit() + + h.flash(_('Updated VCS settings'), category='success') + + except Exception: + log.error(traceback.format_exc()) + h.flash(_('error occurred during updating ' + 'application settings'), category='error') if setting_id == 'hooks': ui_key = request.POST.get('new_hook_ui_key') @@ -256,8 +295,8 @@ class SettingsController(BaseController) if update: h.flash(_('Updated hooks'), category='success') - self.sa.commit() - except: + Session().commit() + except Exception: log.error(traceback.format_exc()) h.flash(_('error occurred during hook creation'), category='error') @@ -293,7 +332,7 @@ class SettingsController(BaseController) if setting_id == 'hooks': hook_id = request.POST.get('hook_id') RhodeCodeUi.delete(hook_id) - self.sa.commit() + Session().commit() @HasPermissionAllDecorator('hg.admin') def show(self, setting_id, format='html'): @@ -326,7 +365,7 @@ class SettingsController(BaseController) # url('admin_settings_my_account') c.user = User.get(self.rhodecode_user.user_id) - all_repos = self.sa.query(Repository)\ + all_repos = Session().query(Repository)\ .filter(Repository.user_id == c.user.user_id)\ .order_by(func.lower(Repository.repo_name)).all() @@ -338,13 +377,16 @@ class SettingsController(BaseController) return redirect(url('users')) defaults = c.user.get_dict() - return htmlfill.render( - render('admin/users/user_edit_my_account.html'), + + c.form = htmlfill.render( + render('admin/users/user_edit_my_account_form.html'), defaults=defaults, encoding="UTF-8", force_defaults=False ) + return render('admin/users/user_edit_my_account.html') + @NotAnonymous() def my_account_update(self): """PUT /_admin/my_account_update: Update an existing item""" # Forms posted to this method should contain a hidden field: @@ -353,32 +395,27 @@ class SettingsController(BaseController) # h.form(url('admin_settings_my_account_update'), # method='put') # url('admin_settings_my_account_update', id=ID) - user_model = UserModel() uid = self.rhodecode_user.user_id + email = self.rhodecode_user.email _form = UserForm(edit=True, - old_data={'user_id': uid, - 'email': self.rhodecode_user.email})() + old_data={'user_id': uid, 'email': email})() form_result = {} try: form_result = _form.to_python(dict(request.POST)) - user_model.update_my_account(uid, form_result) + UserModel().update_my_account(uid, form_result) h.flash(_('Your account was updated successfully'), category='success') - Session.commit() + Session().commit() except formencode.Invalid, errors: c.user = User.get(self.rhodecode_user.user_id) - all_repos = self.sa.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) - return htmlfill.render( - render('admin/users/user_edit_my_account.html'), + c.form = htmlfill.render( + render('admin/users/user_edit_my_account_form.html'), defaults=errors.value, errors=errors.error_dict or {}, prefix_error=False, encoding="UTF-8") + return render('admin/users/user_edit_my_account.html') except Exception: log.error(traceback.format_exc()) h.flash(_('error occurred during update of user %s') \ @@ -387,20 +424,43 @@ 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== + self.rhodecode_user.user_id)\ + .all() + c.participate_in_pull_requests = \ + [x.pull_request for x in PullRequestReviewers.query()\ + .filter(PullRequestReviewers.user_id== + self.rhodecode_user.user_id)\ + .all()] + return render('admin/users/user_edit_my_account_pullrequests.html') + + @NotAnonymous() @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') def create_repository(self): """GET /_admin/create_repository: Form to create a new item""" c.repo_groups = RepoGroup.groups_choices() c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups) + choices, c.landing_revs = ScmModel().get_repo_landing_revs() new_repo = request.GET.get('repo', '') c.new_repo = repo_name_slug(new_repo) return render('admin/repos/repo_add_create_repository.html') - def get_hg_ui_settings(self): - ret = self.sa.query(RhodeCodeUi).all() + def _get_hg_ui_settings(self): + ret = RhodeCodeUi.query().all() if not ret: raise Exception('Could not get application ui settings !') @@ -414,7 +474,7 @@ class SettingsController(BaseController) if k.find('.') != -1: k = k.replace('.', '_') - if each.ui_section == 'hooks': + if each.ui_section in ['hooks', 'extensions']: v = each.ui_active settings[each.ui_section + '_' + k] = v diff --git a/rhodecode/controllers/admin/users.py b/rhodecode/controllers/admin/users.py --- a/rhodecode/controllers/admin/users.py +++ b/rhodecode/controllers/admin/users.py @@ -26,22 +26,28 @@ import logging import traceback import formencode +from pylons import response from formencode import htmlfill from pylons import request, session, tmpl_context as c, url, config from pylons.controllers.util import redirect from pylons.i18n.translation import _ +import rhodecode from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException 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.db import User, Permission +from rhodecode.model.db import User, UserEmailMap from rhodecode.model.forms import UserForm from rhodecode.model.user import UserModel from rhodecode.model.meta import Session +from rhodecode.lib.utils import action_logger +from rhodecode.lib.compat import json +from rhodecode.lib.utils2 import datetime_to_time, str2bool log = logging.getLogger(__name__) @@ -64,7 +70,49 @@ class UsersController(BaseController): """GET /users: All items in the collection""" # url('users') - c.users_list = self.sa.query(User).all() + c.users_list = User.query().order_by(User.username).all() + + users_data = [] + total_records = len(c.users_list) + _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup + template = _tmpl_lookup.get_template('data_table/_dt_elements.html') + + grav_tmpl = lambda user_email, size: ( + template.get_def("user_gravatar") + .render(user_email, size, _=_, h=h, c=c)) + + user_lnk = lambda user_id, username: ( + template.get_def("user_name") + .render(user_id, username, _=_, h=h, c=c)) + + user_actions = lambda user_id, username: ( + template.get_def("user_actions") + .render(user_id, username, _=_, h=h, c=c)) + + for user in c.users_list: + + users_data.append({ + "gravatar": grav_tmpl(user. email, 24), + "raw_username": user.username, + "username": user_lnk(user.user_id, user.username), + "firstname": user.name, + "lastname": user.lastname, + "last_login": h.fmt_date(user.last_login), + "last_login_raw": datetime_to_time(user.last_login), + "active": h.bool2icon(user.active), + "admin": h.bool2icon(user.admin), + "ldap": h.bool2icon(bool(user.ldap_dn)), + "action": user_actions(user.user_id, user.username), + }) + + c.data = json.dumps({ + "totalRecords": total_records, + "startIndex": 0, + "sort": None, + "dir": "asc", + "records": users_data + }) + return render('admin/users/users.html') def create(self): @@ -76,10 +124,12 @@ class UsersController(BaseController): try: form_result = user_form.to_python(dict(request.POST)) user_model.create(form_result) - h.flash(_('created user %s') % form_result['username'], + usr = form_result['username'] + action_logger(self.rhodecode_user, 'admin_created_user:%s' % usr, + None, self.ip_addr, self.sa) + h.flash(_('created user %s') % usr, category='success') - Session.commit() - #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa) + Session().commit() except formencode.Invalid, errors: return htmlfill.render( render('admin/users/user_add.html'), @@ -108,22 +158,31 @@ class UsersController(BaseController): # url('user', id=ID) user_model = UserModel() c.user = user_model.get(id) - + c.perm_user = AuthUser(user_id=id) _form = UserForm(edit=True, old_data={'user_id': id, 'email': c.user.email})() form_result = {} try: form_result = _form.to_python(dict(request.POST)) user_model.update(id, form_result) + usr = form_result['username'] + action_logger(self.rhodecode_user, 'admin_updated_user:%s' % usr, + None, self.ip_addr, self.sa) h.flash(_('User updated successfully'), category='success') - Session.commit() + Session().commit() except formencode.Invalid, errors: + c.user_email_map = UserEmailMap.query()\ + .filter(UserEmailMap.user == c.user).all() + defaults = errors.value e = errors.error_dict or {} - perm = Permission.get_by_key('hg.create.repository') - e.update({'create_repo_perm': user_model.has_perm(id, perm)}) + defaults.update({ + 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'), + 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'), + '_method': 'put' + }) return htmlfill.render( render('admin/users/user_edit.html'), - defaults=errors.value, + defaults=defaults, errors=e, prefix_error=False, encoding="UTF-8") @@ -131,8 +190,7 @@ class UsersController(BaseController): log.error(traceback.format_exc()) h.flash(_('error occurred during update of user %s') \ % form_result.get('username'), category='error') - - return redirect(url('users')) + return redirect(url('edit_user', id=id)) def delete(self, id): """DELETE /users/id: Delete an existing item""" @@ -142,10 +200,10 @@ class UsersController(BaseController): # h.form(url('delete_user', id=ID), # method='delete') # url('user', id=ID) - user_model = UserModel() + usr = User.get_or_404(id) try: - user_model.delete(id) - Session.commit() + UserModel().delete(usr) + Session().commit() h.flash(_('successfully deleted user'), category='success') except (UserOwnsReposException, DefaultUserException), e: h.flash(e, category='warning') @@ -162,19 +220,24 @@ class UsersController(BaseController): def edit(self, id, format='html'): """GET /users/id/edit: Form to edit an existing item""" # url('edit_user', id=ID) - c.user = User.get(id) - if not c.user: - return redirect(url('users')) + c.user = User.get_or_404(id) + if c.user.username == 'default': h.flash(_("You can't edit this user"), category='warning') return redirect(url('users')) + + c.perm_user = AuthUser(user_id=id) 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() + user_model = UserModel() defaults = c.user.get_dict() - perm = Permission.get_by_key('hg.create.repository') - defaults.update({'create_repo_perm': UserModel().has_perm(id, perm)}) + defaults.update({ + 'create_repo_perm': user_model.has_perm(id, 'hg.create.repository'), + 'fork_repo_perm': user_model.has_perm(id, 'hg.fork.repository'), + }) return htmlfill.render( render('admin/users/user_edit.html'), @@ -186,26 +249,72 @@ class UsersController(BaseController): def update_perm(self, id): """PUT /users_perm/id: Update an existing item""" # url('user_perm', id=ID, method='put') + usr = User.get_or_404(id) + grant_create_perm = str2bool(request.POST.get('create_repo_perm')) + grant_fork_perm = str2bool(request.POST.get('fork_repo_perm')) + inherit_perms = str2bool(request.POST.get('inherit_default_permissions')) - grant_perm = request.POST.get('create_repo_perm', False) user_model = UserModel() - if grant_perm: - perm = Permission.get_by_key('hg.create.none') - user_model.revoke_perm(id, perm) + try: + usr.inherit_default_permissions = inherit_perms + Session().add(usr) + + if grant_create_perm: + user_model.revoke_perm(usr, 'hg.create.none') + user_model.grant_perm(usr, 'hg.create.repository') + h.flash(_("Granted 'repository create' permission to user"), + category='success') + else: + user_model.revoke_perm(usr, 'hg.create.repository') + user_model.grant_perm(usr, 'hg.create.none') + h.flash(_("Revoked 'repository create' permission to user"), + category='success') + + if grant_fork_perm: + user_model.revoke_perm(usr, 'hg.fork.none') + user_model.grant_perm(usr, 'hg.fork.repository') + h.flash(_("Granted 'repository fork' permission to user"), + category='success') + else: + user_model.revoke_perm(usr, 'hg.fork.repository') + user_model.grant_perm(usr, 'hg.fork.none') + h.flash(_("Revoked 'repository fork' permission to user"), + category='success') - perm = Permission.get_by_key('hg.create.repository') - user_model.grant_perm(id, perm) - h.flash(_("Granted 'repository create' permission to user"), - category='success') - Session.commit() - else: - perm = Permission.get_by_key('hg.create.repository') - user_model.revoke_perm(id, perm) + Session().commit() + except Exception: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during permissions saving'), + category='error') + return redirect(url('edit_user', id=id)) + + def add_email(self, id): + """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() - perm = Permission.get_by_key('hg.create.none') - user_model.grant_perm(id, perm) - h.flash(_("Revoked 'repository create' permission to user"), - category='success') - Session.commit() + try: + user_model.add_extra_email(id, email) + Session().commit() + h.flash(_("Added email %s to user") % email, category='success') + except formencode.Invalid, error: + msg = error.error_dict['email'] + h.flash(msg, category='error') + except Exception: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during email saving'), + category='error') return redirect(url('edit_user', id=id)) + + def delete_email(self, id): + """DELETE /user_emails_delete/id: Delete an existing item""" + # url('user_emails_delete', id=ID, method='delete') + user_model = UserModel() + user_model.delete_extra_email(id, request.POST.get('del_email')) + Session().commit() + h.flash(_("Removed email from user"), category='success') + return redirect(url('edit_user', id=id)) diff --git a/rhodecode/controllers/admin/users_groups.py b/rhodecode/controllers/admin/users_groups.py --- a/rhodecode/controllers/admin/users_groups.py +++ b/rhodecode/controllers/admin/users_groups.py @@ -34,15 +34,16 @@ from pylons.i18n.translation import _ from rhodecode.lib import helpers as h from rhodecode.lib.exceptions import UsersGroupsAssignedException -from rhodecode.lib.utils2 import safe_unicode +from rhodecode.lib.utils2 import safe_unicode, str2bool from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator from rhodecode.lib.base import BaseController, render from rhodecode.model.users_group import UsersGroupModel -from rhodecode.model.db import User, UsersGroup, Permission, UsersGroupToPerm +from rhodecode.model.db import User, UsersGroup from rhodecode.model.forms import UsersGroupForm from rhodecode.model.meta import Session +from rhodecode.lib.utils import action_logger log = logging.getLogger(__name__) @@ -64,7 +65,7 @@ class UsersGroupsController(BaseControll def index(self, format='html'): """GET /users_groups: All items in the collection""" # url('users_groups') - c.users_groups_list = self.sa.query(UsersGroup).all() + c.users_groups_list = UsersGroup().query().all() return render('admin/users_groups/users_groups.html') def create(self): @@ -76,10 +77,12 @@ class UsersGroupsController(BaseControll form_result = users_group_form.to_python(dict(request.POST)) UsersGroupModel().create(name=form_result['users_group_name'], active=form_result['users_group_active']) - h.flash(_('created users group %s') \ - % form_result['users_group_name'], category='success') - #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa) - Session.commit() + gr = form_result['users_group_name'] + action_logger(self.rhodecode_user, + 'admin_created_users_group:%s' % gr, + None, self.ip_addr, self.sa) + h.flash(_('created users group %s') % gr, category='success') + Session().commit() except formencode.Invalid, errors: return htmlfill.render( render('admin/users_groups/users_group_add.html'), @@ -114,7 +117,7 @@ class UsersGroupsController(BaseControll c.group_members_obj] c.available_members = [(x.user_id, x.username) for x in - self.sa.query(User).all()] + User.query().all()] available_members = [safe_unicode(x[0]) for x in c.available_members] @@ -125,21 +128,27 @@ class UsersGroupsController(BaseControll try: form_result = users_group_form.to_python(request.POST) UsersGroupModel().update(c.users_group, form_result) - h.flash(_('updated users group %s') \ - % form_result['users_group_name'], - category='success') - #action_logger(self.rhodecode_user, 'new_user', '', '', self.sa) - Session.commit() + gr = form_result['users_group_name'] + action_logger(self.rhodecode_user, + 'admin_updated_users_group:%s' % gr, + None, self.ip_addr, self.sa) + h.flash(_('updated users group %s') % gr, category='success') + Session().commit() except formencode.Invalid, errors: + ug_model = UsersGroupModel() + defaults = errors.value e = errors.error_dict or {} - - perm = Permission.get_by_key('hg.create.repository') - e.update({'create_repo_perm': - UsersGroupModel().has_perm(id, perm)}) + defaults.update({ + 'create_repo_perm': ug_model.has_perm(id, + 'hg.create.repository'), + 'fork_repo_perm': ug_model.has_perm(id, + 'hg.fork.repository'), + '_method': 'put' + }) return htmlfill.render( render('admin/users_groups/users_group_edit.html'), - defaults=errors.value, + defaults=defaults, errors=e, prefix_error=False, encoding="UTF-8") @@ -148,7 +157,7 @@ class UsersGroupsController(BaseControll h.flash(_('error occurred during update of users group %s') \ % request.POST.get('users_group_name'), category='error') - return redirect(url('users_groups')) + return redirect(url('edit_users_group', id=id)) def delete(self, id): """DELETE /users_groups/id: Delete an existing item""" @@ -158,10 +167,10 @@ class UsersGroupsController(BaseControll # h.form(url('users_group', id=ID), # method='delete') # url('users_group', id=ID) - + usr_gr = UsersGroup.get_or_404(id) try: - UsersGroupModel().delete(id) - Session.commit() + UsersGroupModel().delete(usr_gr) + Session().commit() h.flash(_('successfully deleted users group'), category='success') except UsersGroupsAssignedException, e: h.flash(e, category='error') @@ -179,20 +188,23 @@ class UsersGroupsController(BaseControll """GET /users_groups/id/edit: Form to edit an existing item""" # url('edit_users_group', id=ID) - c.users_group = self.sa.query(UsersGroup).get(id) - if not c.users_group: - return redirect(url('users_groups')) + c.users_group = UsersGroup.get_or_404(id) c.users_group.permissions = {} c.group_members_obj = [x.user for x in c.users_group.members] c.group_members = [(x.user_id, x.username) for x in c.group_members_obj] c.available_members = [(x.user_id, x.username) for x in - self.sa.query(User).all()] + User.query().all()] + ug_model = UsersGroupModel() defaults = c.users_group.get_dict() - perm = Permission.get_by_key('hg.create.repository') - defaults.update({'create_repo_perm': - UsersGroupModel().has_perm(c.users_group, perm)}) + defaults.update({ + 'create_repo_perm': ug_model.has_perm(c.users_group, + 'hg.create.repository'), + 'fork_repo_perm': ug_model.has_perm(c.users_group, + 'hg.fork.repository'), + }) + return htmlfill.render( render('admin/users_groups/users_group_edit.html'), defaults=defaults, @@ -204,25 +216,43 @@ class UsersGroupsController(BaseControll """PUT /users_perm/id: Update an existing item""" # url('users_group_perm', id=ID, method='put') - grant_perm = request.POST.get('create_repo_perm', False) + users_group = UsersGroup.get_or_404(id) + grant_create_perm = str2bool(request.POST.get('create_repo_perm')) + grant_fork_perm = str2bool(request.POST.get('fork_repo_perm')) + inherit_perms = str2bool(request.POST.get('inherit_default_permissions')) - if grant_perm: - perm = Permission.get_by_key('hg.create.none') - UsersGroupModel().revoke_perm(id, perm) + usersgroup_model = UsersGroupModel() - perm = Permission.get_by_key('hg.create.repository') - UsersGroupModel().grant_perm(id, perm) - h.flash(_("Granted 'repository create' permission to user"), - category='success') + try: + users_group.inherit_default_permissions = inherit_perms + Session().add(users_group) - Session.commit() - else: - perm = Permission.get_by_key('hg.create.repository') - UsersGroupModel().revoke_perm(id, perm) + if grant_create_perm: + usersgroup_model.revoke_perm(id, 'hg.create.none') + usersgroup_model.grant_perm(id, 'hg.create.repository') + h.flash(_("Granted 'repository create' permission to users group"), + category='success') + else: + usersgroup_model.revoke_perm(id, 'hg.create.repository') + usersgroup_model.grant_perm(id, 'hg.create.none') + h.flash(_("Revoked 'repository create' permission to users group"), + category='success') - perm = Permission.get_by_key('hg.create.none') - UsersGroupModel().grant_perm(id, perm) - h.flash(_("Revoked 'repository create' permission to user"), - category='success') - Session.commit() + if grant_fork_perm: + usersgroup_model.revoke_perm(id, 'hg.fork.none') + usersgroup_model.grant_perm(id, 'hg.fork.repository') + h.flash(_("Granted 'repository fork' permission to users group"), + category='success') + else: + usersgroup_model.revoke_perm(id, 'hg.fork.repository') + usersgroup_model.grant_perm(id, 'hg.fork.none') + h.flash(_("Revoked 'repository fork' permission to users group"), + category='success') + + Session().commit() + except Exception: + log.error(traceback.format_exc()) + h.flash(_('An error occurred during permissions saving'), + category='error') + return redirect(url('edit_users_group', id=id)) diff --git a/rhodecode/controllers/api/__init__.py b/rhodecode/controllers/api/__init__.py --- a/rhodecode/controllers/api/__init__.py +++ b/rhodecode/controllers/api/__init__.py @@ -30,6 +30,7 @@ import logging import types import urllib import traceback +import time from rhodecode.lib.compat import izip_longest, json @@ -43,6 +44,8 @@ HTTPBadRequest, HTTPError from rhodecode.model.db import User from rhodecode.lib.auth import AuthUser +from rhodecode.lib.base import _get_ip_addr, _get_access_path +from rhodecode.lib.utils2 import safe_unicode log = logging.getLogger('JSONRPC') @@ -57,15 +60,16 @@ class JSONRPCError(BaseException): return str(self.message) -def jsonrpc_error(message, code=None): +def jsonrpc_error(message, retid=None, code=None): """ Generate a Response object with a JSON-RPC error body """ from pylons.controllers.util import Response - resp = Response(body=json.dumps(dict(id=None, result=None, error=message)), - status=code, - content_type='application/json') - return resp + return Response( + body=json.dumps(dict(id=retid, result=None, error=message)), + status=code, + content_type='application/json' + ) class JSONRPCController(WSGIController): @@ -94,9 +98,12 @@ class JSONRPCController(WSGIController): Parse the request body as JSON, look up the method on the controller and if it exists, dispatch to it. """ + start = time.time() + self._req_id = None if 'CONTENT_LENGTH' not in environ: log.debug("No Content-Length") - return jsonrpc_error(message="No Content-Length in request") + return jsonrpc_error(retid=self._req_id, + message="No Content-Length in request") else: length = environ['CONTENT_LENGTH'] or 0 length = int(environ['CONTENT_LENGTH']) @@ -104,7 +111,8 @@ class JSONRPCController(WSGIController): if length == 0: log.debug("Content-Length is 0") - return jsonrpc_error(message="Content-Length is 0") + return jsonrpc_error(retid=self._req_id, + message="Content-Length is 0") raw_body = environ['wsgi.input'].read(length) @@ -112,7 +120,8 @@ class JSONRPCController(WSGIController): json_body = json.loads(urllib.unquote_plus(raw_body)) except ValueError, e: # catch JSON errors Here - return jsonrpc_error(message="JSON parse error ERR:%s RAW:%r" \ + return jsonrpc_error(retid=self._req_id, + message="JSON parse error ERR:%s RAW:%r" \ % (e, urllib.unquote_plus(raw_body))) # check AUTH based on API KEY @@ -126,22 +135,26 @@ class JSONRPCController(WSGIController): self._request_params) ) except KeyError, e: - return jsonrpc_error(message='Incorrect JSON query missing %s' % e) + return jsonrpc_error(retid=self._req_id, + message='Incorrect JSON query missing %s' % e) # check if we can find this session using api_key try: u = User.get_by_api_key(self._req_api_key) if u is None: - return jsonrpc_error(message='Invalid API KEY') + return jsonrpc_error(retid=self._req_id, + message='Invalid API KEY') auth_u = AuthUser(u.user_id, self._req_api_key) except Exception, e: - return jsonrpc_error(message='Invalid API KEY') + return jsonrpc_error(retid=self._req_id, + message='Invalid API KEY') self._error = None try: self._func = self._find_method() except AttributeError, e: - return jsonrpc_error(message=str(e)) + return jsonrpc_error(retid=self._req_id, + message=str(e)) # now that we have a method, add self._req_params to # self.kargs and dispatch control to WGIController @@ -164,9 +177,12 @@ class JSONRPCController(WSGIController): USER_SESSION_ATTR = 'apiuser' if USER_SESSION_ATTR not in arglist: - return jsonrpc_error(message='This method [%s] does not support ' - 'authentication (missing %s param)' % - (self._func.__name__, USER_SESSION_ATTR)) + return jsonrpc_error( + retid=self._req_id, + message='This method [%s] does not support ' + 'authentication (missing %s param)' % ( + self._func.__name__, USER_SESSION_ATTR) + ) # get our arglist and check if we provided them as args for arg, default in func_kwargs.iteritems(): @@ -179,6 +195,7 @@ class JSONRPCController(WSGIController): # NotImplementedType (default_empty) if (default == default_empty and arg not in self._request_params): return jsonrpc_error( + retid=self._req_id, message=( 'Missing non optional `%s` arg in JSON DATA' % arg ) @@ -205,7 +222,10 @@ class JSONRPCController(WSGIController): headers.append(('Content-Length', str(len(output[0])))) replace_header(headers, 'Content-Type', 'application/json') start_response(status[0], headers, exc_info[0]) - + log.info('IP: %s Request to %s time: %.3fs' % ( + _get_ip_addr(environ), + safe_unicode(_get_access_path(environ)), time.time() - start) + ) return output def _dispatch_call(self): diff --git a/rhodecode/controllers/api/api.py b/rhodecode/controllers/api/api.py --- a/rhodecode/controllers/api/api.py +++ b/rhodecode/controllers/api/api.py @@ -31,18 +31,100 @@ import logging from rhodecode.controllers.api import JSONRPCController, JSONRPCError from rhodecode.lib.auth import HasPermissionAllDecorator, \ HasPermissionAnyDecorator, PasswordGenerator, AuthUser - +from rhodecode.lib.utils import map_groups, repo2db_mapper from rhodecode.model.meta import Session from rhodecode.model.scm import ScmModel -from rhodecode.model.db import User, UsersGroup, Repository from rhodecode.model.repo import RepoModel from rhodecode.model.user import UserModel from rhodecode.model.users_group import UsersGroupModel -from rhodecode.lib.utils import map_groups +from rhodecode.model.permission import PermissionModel +from rhodecode.model.db import Repository log = logging.getLogger(__name__) +class Optional(object): + """ + Defines an optional parameter:: + + param = param.getval() if isinstance(param, Optional) else param + param = param() if isinstance(param, Optional) else param + + is equivalent of:: + + param = Optional.extract(param) + + """ + def __init__(self, type_): + self.type_ = type_ + + def __repr__(self): + return '' % self.type_.__repr__() + + def __call__(self): + return self.getval() + + def getval(self): + """ + returns value from this Optional instance + """ + return self.type_ + + @classmethod + def extract(cls, val): + if isinstance(val, cls): + return val.getval() + return val + + +def get_user_or_error(userid): + """ + Get user by id or name or return JsonRPCError if not found + + :param userid: + """ + user = UserModel().get_user(userid) + if user is None: + raise JSONRPCError("user `%s` does not exist" % userid) + return user + + +def get_repo_or_error(repoid): + """ + Get repo by id or name or return JsonRPCError if not found + + :param userid: + """ + repo = RepoModel().get_repo(repoid) + if repo is None: + raise JSONRPCError('repository `%s` does not exist' % (repoid)) + return repo + + +def get_users_group_or_error(usersgroupid): + """ + Get users group by id or name or return JsonRPCError if not found + + :param userid: + """ + users_group = UsersGroupModel().get_group(usersgroupid) + if users_group is None: + raise JSONRPCError('users group `%s` does not exist' % usersgroupid) + return users_group + + +def get_perm_or_error(permid): + """ + Get permission by id or name or return JsonRPCError if not found + + :param userid: + """ + perm = PermissionModel().get_permission_by_name(permid) + if perm is None: + raise JSONRPCError('permission `%s` does not exist' % (permid)) + return perm + + class ApiController(JSONRPCController): """ API Controller @@ -60,23 +142,74 @@ class ApiController(JSONRPCController): """ @HasPermissionAllDecorator('hg.admin') - def pull(self, apiuser, repo_name): + def pull(self, apiuser, repoid): """ Dispatch pull action on given repo + :param apiuser: + :param repoid: + """ - :param user: - :param repo_name: + repo = get_repo_or_error(repoid) + + try: + ScmModel().pull_changes(repo.repo_name, + self.rhodecode_user.username) + return 'Pulled from `%s`' % repo.repo_name + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'Unable to pull changes from `%s`' % repo.repo_name + ) + + @HasPermissionAllDecorator('hg.admin') + def rescan_repos(self, apiuser, remove_obsolete=Optional(False)): + """ + Dispatch rescan repositories action. If remove_obsolete is set + than also delete repos that are in database but not in the filesystem. + aka "clean zombies" + + :param apiuser: + :param remove_obsolete: """ - if Repository.is_valid(repo_name) is False: - raise JSONRPCError('Unknown repo "%s"' % repo_name) - try: - ScmModel().pull_changes(repo_name, self.rhodecode_user.username) - return 'Pulled from %s' % repo_name + rm_obsolete = Optional.extract(remove_obsolete) + added, removed = repo2db_mapper(ScmModel().repo_scan(), + remove_obsolete=rm_obsolete) + return {'added': added, 'removed': removed} except Exception: - raise JSONRPCError('Unable to pull changes from "%s"' % repo_name) + log.error(traceback.format_exc()) + raise JSONRPCError( + 'Error occurred during rescan repositories action' + ) + + @HasPermissionAllDecorator('hg.admin') + def lock(self, apiuser, repoid, userid, locked): + """ + Set locking state on particular repository by given user + + :param apiuser: + :param repoid: + :param userid: + :param locked: + """ + repo = get_repo_or_error(repoid) + user = get_user_or_error(userid) + locked = bool(locked) + try: + if locked: + Repository.lock(repo, user.user_id) + else: + Repository.unlock(repo) + + return ('User `%s` set lock state for repo `%s` to `%s`' + % (user.username, repo.repo_name, locked)) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'Error occurred locking repository `%s`' % repo.repo_name + ) @HasPermissionAllDecorator('hg.admin') def get_user(self, apiuser, userid): @@ -84,25 +217,13 @@ class ApiController(JSONRPCController): Get a user by username :param apiuser: - :param username: + :param userid: """ - user = UserModel().get_user(userid) - if user is None: - return user - - return dict( - id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - active=user.active, - admin=user.admin, - ldap_dn=user.ldap_dn, - last_login=user.last_login, - permissions=AuthUser(user_id=user.user_id).permissions - ) + user = get_user_or_error(userid) + data = user.get_api_data() + data['permissions'] = AuthUser(user_id=user.user_id).permissions + return data @HasPermissionAllDecorator('hg.admin') def get_users(self, apiuser): @@ -113,124 +234,150 @@ class ApiController(JSONRPCController): """ result = [] - for user in User.getAll(): - result.append( - dict( - id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - active=user.active, - admin=user.admin, - ldap_dn=user.ldap_dn, - last_login=user.last_login, - ) - ) + for user in UserModel().get_all(): + result.append(user.get_api_data()) return result @HasPermissionAllDecorator('hg.admin') - def create_user(self, apiuser, username, email, password, firstname=None, - lastname=None, active=True, admin=False, ldap_dn=None): + def create_user(self, apiuser, username, email, password, + firstname=Optional(None), lastname=Optional(None), + active=Optional(True), admin=Optional(False), + ldap_dn=Optional(None)): """ Create new user :param apiuser: :param username: + :param email: :param password: - :param email: - :param name: + :param firstname: :param lastname: :param active: :param admin: :param ldap_dn: """ - if User.get_by_username(username): - raise JSONRPCError("user %s already exist" % username) + + if UserModel().get_by_username(username): + raise JSONRPCError("user `%s` already exist" % username) - if User.get_by_email(email, case_insensitive=True): - raise JSONRPCError("email %s already exist" % email) + if UserModel().get_by_email(email, case_insensitive=True): + raise JSONRPCError("email `%s` already exist" % email) - if ldap_dn: + if Optional.extract(ldap_dn): # generate temporary password if ldap_dn password = PasswordGenerator().gen_password(length=8) try: - usr = UserModel().create_or_update( - username, password, email, firstname, - lastname, active, admin, ldap_dn + user = UserModel().create_or_update( + username=Optional.extract(username), + password=Optional.extract(password), + email=Optional.extract(email), + firstname=Optional.extract(firstname), + lastname=Optional.extract(lastname), + active=Optional.extract(active), + admin=Optional.extract(admin), + ldap_dn=Optional.extract(ldap_dn) ) - Session.commit() + Session().commit() return dict( - id=usr.user_id, - msg='created new user %s' % username + msg='created new user `%s`' % username, + user=user.get_api_data() ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create user %s' % username) + raise JSONRPCError('failed to create user `%s`' % username) @HasPermissionAllDecorator('hg.admin') - def update_user(self, apiuser, userid, username, password, email, - firstname, lastname, active, admin, ldap_dn): + def update_user(self, apiuser, userid, username=Optional(None), + email=Optional(None), firstname=Optional(None), + lastname=Optional(None), active=Optional(None), + admin=Optional(None), ldap_dn=Optional(None), + password=Optional(None)): """ Updates given user :param apiuser: + :param userid: :param username: - :param password: :param email: - :param name: + :param firstname: :param lastname: :param active: :param admin: :param ldap_dn: + :param password: """ - if not UserModel().get_user(userid): - raise JSONRPCError("user %s does not exist" % username) + + user = get_user_or_error(userid) + + # call function and store only updated arguments + updates = {} + + def store_update(attr, name): + if not isinstance(attr, Optional): + updates[name] = attr try: - usr = UserModel().create_or_update( - username, password, email, firstname, - lastname, active, admin, ldap_dn - ) - Session.commit() + + store_update(username, 'username') + store_update(password, 'password') + store_update(email, 'email') + store_update(firstname, 'name') + store_update(lastname, 'lastname') + store_update(active, 'active') + store_update(admin, 'admin') + store_update(ldap_dn, 'ldap_dn') + + user = UserModel().update_user(user, **updates) + Session().commit() return dict( - id=usr.user_id, - msg='updated user %s' % username + msg='updated user ID:%s %s' % (user.user_id, user.username), + user=user.get_api_data() ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to update user %s' % username) + raise JSONRPCError('failed to update user `%s`' % userid) @HasPermissionAllDecorator('hg.admin') - def get_users_group(self, apiuser, group_name): + def delete_user(self, apiuser, userid): """" - Get users group by name + Deletes an user :param apiuser: - :param group_name: + :param userid: """ + user = get_user_or_error(userid) - users_group = UsersGroup.get_by_group_name(group_name) - if not users_group: - return None + try: + UserModel().delete(userid) + Session().commit() + return dict( + msg='deleted user ID:%s %s' % (user.user_id, user.username), + user=None + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError('failed to delete ID:%s %s' % (user.user_id, + user.username)) + + @HasPermissionAllDecorator('hg.admin') + def get_users_group(self, apiuser, usersgroupid): + """" + Get users group by name or id + + :param apiuser: + :param usersgroupid: + """ + users_group = get_users_group_or_error(usersgroupid) + + data = users_group.get_api_data() members = [] for user in users_group.members: user = user.user - members.append(dict(id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - active=user.active, - admin=user.admin, - ldap=user.ldap_dn)) - - return dict(id=users_group.users_group_id, - group_name=users_group.users_group_name, - active=users_group.users_group_active, - members=members) + members.append(user.get_api_data()) + data['members'] = members + return data @HasPermissionAllDecorator('hg.admin') def get_users_groups(self, apiuser): @@ -241,107 +388,96 @@ class ApiController(JSONRPCController): """ result = [] - for users_group in UsersGroup.getAll(): - members = [] - for user in users_group.members: - user = user.user - members.append(dict(id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - active=user.active, - admin=user.admin, - ldap=user.ldap_dn)) - - result.append(dict(id=users_group.users_group_id, - group_name=users_group.users_group_name, - active=users_group.users_group_active, - members=members)) + for users_group in UsersGroupModel().get_all(): + result.append(users_group.get_api_data()) return result @HasPermissionAllDecorator('hg.admin') - def create_users_group(self, apiuser, group_name, active=True): + def create_users_group(self, apiuser, group_name, active=Optional(True)): """ Creates an new usergroup + :param apiuser: :param group_name: :param active: """ - if self.get_users_group(apiuser, group_name): - raise JSONRPCError("users group %s already exist" % group_name) + if UsersGroupModel().get_by_name(group_name): + raise JSONRPCError("users group `%s` already exist" % group_name) try: + active = Optional.extract(active) ug = UsersGroupModel().create(name=group_name, active=active) - Session.commit() - return dict(id=ug.users_group_id, - msg='created new users group %s' % group_name) + Session().commit() + return dict( + msg='created new users group `%s`' % group_name, + users_group=ug.get_api_data() + ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to create group %s' % group_name) + raise JSONRPCError('failed to create group `%s`' % group_name) @HasPermissionAllDecorator('hg.admin') - def add_user_to_users_group(self, apiuser, group_name, username): + def add_user_to_users_group(self, apiuser, usersgroupid, userid): """" Add a user to a users group :param apiuser: - :param group_name: - :param username: + :param usersgroupid: + :param userid: """ + user = get_user_or_error(userid) + users_group = get_users_group_or_error(usersgroupid) try: - users_group = UsersGroup.get_by_group_name(group_name) - if not users_group: - raise JSONRPCError('unknown users group %s' % group_name) - - user = User.get_by_username(username) - if user is None: - raise JSONRPCError('unknown user %s' % username) - ugm = UsersGroupModel().add_user_to_group(users_group, user) success = True if ugm != True else False - msg = 'added member %s to users group %s' % (username, group_name) + msg = 'added member `%s` to users group `%s`' % ( + user.username, users_group.users_group_name + ) msg = msg if success else 'User is already in that group' - Session.commit() + Session().commit() return dict( - id=ugm.users_group_member_id if ugm != True else None, success=success, msg=msg ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to add users group member') + raise JSONRPCError( + 'failed to add member to users group `%s`' % ( + users_group.users_group_name + ) + ) @HasPermissionAllDecorator('hg.admin') - def remove_user_from_users_group(self, apiuser, group_name, username): + def remove_user_from_users_group(self, apiuser, usersgroupid, userid): """ Remove user from a group - :param apiuser - :param group_name - :param username + :param apiuser: + :param usersgroupid: + :param userid: """ + user = get_user_or_error(userid) + users_group = get_users_group_or_error(usersgroupid) try: - users_group = UsersGroup.get_by_group_name(group_name) - if not users_group: - raise JSONRPCError('unknown users group %s' % group_name) - - user = User.get_by_username(username) - if user is None: - raise JSONRPCError('unknown user %s' % username) - - success = UsersGroupModel().remove_user_from_group(users_group, user) - msg = 'removed member %s from users group %s' % (username, group_name) + success = UsersGroupModel().remove_user_from_group(users_group, + user) + msg = 'removed member `%s` from users group `%s`' % ( + user.username, users_group.users_group_name + ) msg = msg if success else "User wasn't in group" - Session.commit() + Session().commit() return dict(success=success, msg=msg) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to remove user from group') + raise JSONRPCError( + 'failed to remove member from users group `%s`' % ( + users_group.users_group_name + ) + ) @HasPermissionAnyDecorator('hg.admin') def get_repo(self, apiuser, repoid): @@ -349,51 +485,30 @@ class ApiController(JSONRPCController): Get repository by name :param apiuser: - :param repo_name: + :param repoid: """ - - repo = RepoModel().get_repo(repoid) - if repo is None: - raise JSONRPCError('unknown repository %s' % repo) + repo = get_repo_or_error(repoid) members = [] for user in repo.repo_to_perm: perm = user.permission.permission_name user = user.user - members.append( - dict( - type="user", - id=user.user_id, - username=user.username, - firstname=user.name, - lastname=user.lastname, - email=user.email, - active=user.active, - admin=user.admin, - ldap=user.ldap_dn, - permission=perm - ) - ) + 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 - members.append( - dict( - type="users_group", - id=users_group.users_group_id, - name=users_group.users_group_name, - active=users_group.users_group_active, - permission=perm - ) - ) + users_group_data = users_group.get_api_data() + users_group_data['type'] = "users_group" + users_group_data['permission'] = perm + members.append(users_group_data) - return dict( - id=repo.repo_id, - repo_name=repo.repo_name, - type=repo.repo_type, - description=repo.description, - members=members - ) + data = repo.get_api_data() + data['members'] = members + return data @HasPermissionAnyDecorator('hg.admin') def get_repos(self, apiuser): @@ -404,19 +519,12 @@ class ApiController(JSONRPCController): """ result = [] - for repository in Repository.getAll(): - result.append( - dict( - id=repository.repo_id, - repo_name=repository.repo_name, - type=repository.repo_type, - description=repository.description - ) - ) + for repo in RepoModel().get_all(): + result.append(repo.get_api_data()) return result @HasPermissionAnyDecorator('hg.admin') - def get_repo_nodes(self, apiuser, repo_name, revision, root_path, + def get_repo_nodes(self, apiuser, repoid, revision, root_path, ret_type='all'): """ returns a list of nodes and it's children @@ -424,13 +532,14 @@ class ApiController(JSONRPCController): to show only files or dirs :param apiuser: - :param repo_name: name of repository + :param repoid: name or id of repository :param revision: revision for which listing should be done :param root_path: path from which start displaying :param ret_type: return type 'all|files|dirs' nodes """ + repo = get_repo_or_error(repoid) try: - _d, _f = ScmModel().get_nodes(repo_name, revision, root_path, + _d, _f = ScmModel().get_nodes(repo, revision, root_path, flat=False) _map = { 'all': _d + _f, @@ -440,218 +549,264 @@ class ApiController(JSONRPCController): return _map[ret_type] except KeyError: raise JSONRPCError('ret_type must be one of %s' % _map.keys()) - except Exception, e: - raise JSONRPCError(e) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to get repo: `%s` nodes' % repo.repo_name + ) @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository') - def create_repo(self, apiuser, repo_name, owner_name, description='', - repo_type='hg', private=False, clone_uri=None): + def create_repo(self, apiuser, repo_name, owner, repo_type, + description=Optional(''), private=Optional(False), + clone_uri=Optional(None), landing_rev=Optional('tip')): """ Create repository, if clone_url is given it makes a remote clone + if repo_name is withina group name the groups will be created + automatically if they aren't present :param apiuser: :param repo_name: - :param owner_name: + :param onwer: + :param repo_type: :param description: - :param repo_type: :param private: :param clone_uri: + :param landing_rev: """ + 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) + clone_uri = Optional.extract(clone_uri) + description = Optional.extract(description) + landing_rev = Optional.extract(landing_rev) try: - owner = User.get_by_username(owner_name) - if owner is None: - raise JSONRPCError('unknown user %s' % owner_name) - - if Repository.get_by_repo_name(repo_name): - raise JSONRPCError("repo %s already exist" % repo_name) - - groups = repo_name.split(Repository.url_sep()) - real_name = groups[-1] - # create structure of groups + # create structure of groups and return the last group group = map_groups(repo_name) - repo = RepoModel().create( - dict( - repo_name=real_name, - repo_name_full=repo_name, - description=description, - private=private, - repo_type=repo_type, - repo_group=group.group_id if group else None, - clone_uri=clone_uri - ), - owner + repo = RepoModel().create_repo( + repo_name=repo_name, + repo_type=repo_type, + description=description, + owner=owner, + private=private, + clone_uri=clone_uri, + repos_group=group, + landing_rev=landing_rev, ) - Session.commit() + + Session().commit() return dict( - id=repo.repo_id, - msg="Created new repository %s" % repo.repo_name + 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) + raise JSONRPCError('failed to create repository `%s`' % repo_name) @HasPermissionAnyDecorator('hg.admin') - def delete_repo(self, apiuser, repo_name): - """ - Deletes a given repository + def fork_repo(self, apiuser, repoid, fork_name, owner, + 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)) + + try: + # create structure of groups and return the last group + group = map_groups(fork_name) - :param repo_name: - """ - if not Repository.get_by_repo_name(repo_name): - raise JSONRPCError("repo %s does not exist" % repo_name) - try: - RepoModel().delete(repo_name) - Session.commit() + form_data = dict( + repo_name=fork_name, + repo_name_full=fork_name, + repo_group=group, + repo_type=repo.repo_type, + description=Optional.extract(description), + private=Optional.extract(private), + copy_permissions=Optional.extract(copy_permissions), + landing_rev=Optional.extract(landing_rev), + update_after_clone=False, + fork_parent_id=repo.repo_id, + ) + RepoModel().create_fork(form_data, cur_user=owner) return dict( - msg='Deleted repository %s' % repo_name + msg='Created fork of `%s` as `%s`' % (repo.repo_name, + fork_name), + success=True # cannot return the repo data here since fork + # cann be done async ) except Exception: log.error(traceback.format_exc()) - raise JSONRPCError('failed to delete repository %s' % repo_name) + raise JSONRPCError( + 'failed to fork repository `%s` as `%s`' % (repo_name, + fork_name) + ) @HasPermissionAnyDecorator('hg.admin') - def grant_user_permission(self, apiuser, repo_name, username, perm): + def delete_repo(self, apiuser, repoid): """ - Grant permission for user on given repository, or update existing one - if found + Deletes a given repository - :param repo_name: - :param username: - :param perm: + :param apiuser: + :param repoid: """ + repo = get_repo_or_error(repoid) try: - repo = Repository.get_by_repo_name(repo_name) - if repo is None: - raise JSONRPCError('unknown repository %s' % repo) - - user = User.get_by_username(username) - if user is None: - raise JSONRPCError('unknown user %s' % username) - - RepoModel().grant_user_permission(repo=repo, user=user, perm=perm) - - Session.commit() + RepoModel().delete(repo) + Session().commit() return dict( - msg='Granted perm: %s for user: %s in repo: %s' % ( - perm, username, repo_name - ) + msg='Deleted repository `%s`' % repo.repo_name, + success=True ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( - 'failed to edit permission %(repo)s for %(user)s' % dict( - user=username, repo=repo_name + 'failed to delete repository `%s`' % repo.repo_name + ) + + @HasPermissionAnyDecorator('hg.admin') + def grant_user_permission(self, apiuser, repoid, userid, perm): + """ + Grant permission for user on given repository, or update existing one + if found + + :param repoid: + :param userid: + :param perm: + """ + repo = get_repo_or_error(repoid) + user = get_user_or_error(userid) + perm = get_perm_or_error(perm) + + try: + + RepoModel().grant_user_permission(repo=repo, user=user, perm=perm) + + Session().commit() + return dict( + msg='Granted perm: `%s` for user: `%s` in repo: `%s`' % ( + perm.permission_name, user.username, repo.repo_name + ), + success=True + ) + except Exception: + log.error(traceback.format_exc()) + raise JSONRPCError( + 'failed to edit permission for user: `%s` in repo: `%s`' % ( + userid, repoid ) ) @HasPermissionAnyDecorator('hg.admin') - def revoke_user_permission(self, apiuser, repo_name, username): + def revoke_user_permission(self, apiuser, repoid, userid): """ Revoke permission for user on given repository - :param repo_name: - :param username: + :param apiuser: + :param repoid: + :param userid: """ + repo = get_repo_or_error(repoid) + user = get_user_or_error(userid) try: - repo = Repository.get_by_repo_name(repo_name) - if repo is None: - raise JSONRPCError('unknown repository %s' % repo) + + RepoModel().revoke_user_permission(repo=repo, user=user) - user = User.get_by_username(username) - if user is None: - raise JSONRPCError('unknown user %s' % username) - - RepoModel().revoke_user_permission(repo=repo_name, user=username) - - Session.commit() + Session().commit() return dict( - msg='Revoked perm for user: %s in repo: %s' % ( - username, repo_name - ) + msg='Revoked perm for user: `%s` in repo: `%s`' % ( + user.username, repo.repo_name + ), + success=True ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( - 'failed to edit permission %(repo)s for %(user)s' % dict( - user=username, repo=repo_name + 'failed to edit permission for user: `%s` in repo: `%s`' % ( + userid, repoid ) ) @HasPermissionAnyDecorator('hg.admin') - def grant_users_group_permission(self, apiuser, repo_name, group_name, perm): + def grant_users_group_permission(self, apiuser, repoid, usersgroupid, + perm): """ Grant permission for users group on given repository, or update existing one if found - :param repo_name: - :param group_name: + :param apiuser: + :param repoid: + :param usersgroupid: :param perm: """ + repo = get_repo_or_error(repoid) + perm = get_perm_or_error(perm) + users_group = get_users_group_or_error(usersgroupid) try: - repo = Repository.get_by_repo_name(repo_name) - if repo is None: - raise JSONRPCError('unknown repository %s' % repo) - - user_group = UsersGroup.get_by_group_name(group_name) - if user_group is None: - raise JSONRPCError('unknown users group %s' % user_group) - - RepoModel().grant_users_group_permission(repo=repo_name, - group_name=group_name, + RepoModel().grant_users_group_permission(repo=repo, + group_name=users_group, perm=perm) - Session.commit() + Session().commit() return dict( - msg='Granted perm: %s for group: %s in repo: %s' % ( - perm, group_name, repo_name - ) + msg='Granted perm: `%s` for users group: `%s` in ' + 'repo: `%s`' % ( + perm.permission_name, users_group.users_group_name, + repo.repo_name + ), + success=True ) except Exception: + print traceback.format_exc() log.error(traceback.format_exc()) raise JSONRPCError( - 'failed to edit permission %(repo)s for %(usersgr)s' % dict( - usersgr=group_name, repo=repo_name + 'failed to edit permission for users group: `%s` in ' + 'repo: `%s`' % ( + usersgroupid, repo.repo_name ) ) @HasPermissionAnyDecorator('hg.admin') - def revoke_users_group_permission(self, apiuser, repo_name, group_name): + def revoke_users_group_permission(self, apiuser, repoid, usersgroupid): """ Revoke permission for users group on given repository - :param repo_name: - :param group_name: + :param apiuser: + :param repoid: + :param usersgroupid: """ + repo = get_repo_or_error(repoid) + users_group = get_users_group_or_error(usersgroupid) try: - repo = Repository.get_by_repo_name(repo_name) - if repo is None: - raise JSONRPCError('unknown repository %s' % repo) - - user_group = UsersGroup.get_by_group_name(group_name) - if user_group is None: - raise JSONRPCError('unknown users group %s' % user_group) + RepoModel().revoke_users_group_permission(repo=repo, + group_name=users_group) - RepoModel().revoke_users_group_permission(repo=repo_name, - group_name=group_name) - - Session.commit() + Session().commit() return dict( - msg='Revoked perm for group: %s in repo: %s' % ( - group_name, repo_name - ) + msg='Revoked perm for users group: `%s` in repo: `%s`' % ( + users_group.users_group_name, repo.repo_name + ), + success=True ) except Exception: log.error(traceback.format_exc()) raise JSONRPCError( - 'failed to edit permission %(repo)s for %(usersgr)s' % dict( - usersgr=group_name, repo=repo_name + 'failed to edit permission for users group: `%s` in ' + 'repo: `%s`' % ( + users_group.users_group_name, repo.repo_name ) ) diff --git a/rhodecode/controllers/branches.py b/rhodecode/controllers/branches.py --- a/rhodecode/controllers/branches.py +++ b/rhodecode/controllers/branches.py @@ -24,14 +24,15 @@ # along with this program. If not, see . import logging +import binascii from pylons import tmpl_context as c -import binascii from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.compat import OrderedDict from rhodecode.lib.utils2 import safe_unicode + log = logging.getLogger(__name__) diff --git a/rhodecode/controllers/changelog.py b/rhodecode/controllers/changelog.py --- a/rhodecode/controllers/changelog.py +++ b/rhodecode/controllers/changelog.py @@ -26,7 +26,6 @@ import logging import traceback -from mercurial import graphmod from pylons import request, url, session, tmpl_context as c from pylons.controllers.util import redirect from pylons.i18n.translation import _ @@ -36,9 +35,8 @@ from rhodecode.lib.auth import LoginRequ from rhodecode.lib.base import BaseRepoController, render from rhodecode.lib.helpers import RepoPage from rhodecode.lib.compat import json - +from rhodecode.lib.graphmod import _colored, _dagwalker from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError -from rhodecode.model.db import Repository log = logging.getLogger(__name__) @@ -60,13 +58,13 @@ class ChangelogController(BaseRepoContro int_size = int(request.params.get('size')) except ValueError: int_size = default - int_size = int_size if int_size <= limit else limit - c.size = int_size + c.size = max(min(int_size, limit), 1) session['changelog_size'] = c.size session.save() else: c.size = int(session.get('changelog_size', default)) - + # min size must be 1 + c.size = max(c.size, 1) p = int(request.params.get('page', 1)) branch_name = request.params.get('branch', None) try: @@ -83,8 +81,8 @@ class ChangelogController(BaseRepoContro items_per_page=c.size, branch=branch_name) collection = list(c.pagination) page_revisions = [x.raw_id for x in collection] - c.comments = c.rhodecode_db_repo.comments(page_revisions) - + c.comments = c.rhodecode_db_repo.get_comments(page_revisions) + c.statuses = c.rhodecode_db_repo.statuses(page_revisions) except (RepositoryError, ChangesetDoesNotExistError, Exception), e: log.error(traceback.format_exc()) h.flash(str(e), category='warning') @@ -118,18 +116,9 @@ class ChangelogController(BaseRepoContro data = [] revs = [x.revision for x in collection] - if repo.alias == 'git': - for _ in revs: - vtx = [0, 1] - edges = [[0, 0, 1]] - data.append(['', vtx, edges]) - - elif repo.alias == 'hg': - dag = graphmod.dagwalker(repo._repo, revs) - c.dag = graphmod.colored(dag, repo._repo) - for (id, type, ctx, vtx, edges) in c.dag: - if type != graphmod.CHANGESET: - continue - data.append(['', vtx, edges]) + dag = _dagwalker(repo, revs, repo.alias) + dag = _colored(dag) + for (id, type, ctx, vtx, edges) in dag: + data.append(['', vtx, edges]) c.jsdata = json.dumps(data) diff --git a/rhodecode/controllers/changeset.py b/rhodecode/controllers/changeset.py --- a/rhodecode/controllers/changeset.py +++ b/rhodecode/controllers/changeset.py @@ -40,13 +40,17 @@ from rhodecode.lib.vcs.nodes import File import rhodecode.lib.helpers as h from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController, render -from rhodecode.lib.utils import EmptyChangeset +from rhodecode.lib.utils import action_logger from rhodecode.lib.compat import OrderedDict from rhodecode.lib import diffs -from rhodecode.model.db import ChangesetComment +from rhodecode.model.db import ChangesetComment, ChangesetStatus from rhodecode.model.comment import ChangesetCommentsModel +from rhodecode.model.changeset_status import ChangesetStatusModel from rhodecode.model.meta import Session from rhodecode.lib.diffs import wrapped_diff +from rhodecode.model.repo import RepoModel +from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError +from rhodecode.lib.vcs.backends.base import EmptyChangeset log = logging.getLogger(__name__) @@ -165,6 +169,9 @@ class ChangesetController(BaseRepoContro def __before__(self): super(ChangesetController, self).__before__() c.affected_files_cut_off = 60 + repo_model = RepoModel() + c.users_array = repo_model.get_users_js() + c.users_groups_array = repo_model.get_users_groups_js() def index(self, revision): @@ -201,18 +208,24 @@ class ChangesetController(BaseRepoContro cumulative_diff = 0 c.cut_off = False # defines if cut off limit is reached - + c.changeset_statuses = ChangesetStatus.STATUSES c.comments = [] + c.statuses = [] c.inline_comments = [] c.inline_cnt = 0 # Iterate over ranges (default changeset view is always one changeset) for changeset in c.cs_ranges: + + 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, - changeset.raw_id)) + revision=changeset.raw_id)) inlines = ChangesetCommentsModel()\ .get_inline_comments(c.rhodecode_db_repo.repo_id, - changeset.raw_id) + revision=changeset.raw_id) c.inline_comments.extend(inlines) c.changes[changeset.raw_id] = [] try: @@ -284,7 +297,7 @@ class ChangesetController(BaseRepoContro ) # count inline comments - for path, lines in c.inline_comments: + for __, lines in c.inline_comments: for comments in lines.values(): c.inline_cnt += len(comments) @@ -361,15 +374,48 @@ class ChangesetController(BaseRepoContro @jsonify def comment(self, repo_name, revision): + status = request.POST.get('changeset_status') + change_status = request.POST.get('change_changeset_status') + comm = ChangesetCommentsModel().create( text=request.POST.get('text'), - repo_id=c.rhodecode_db_repo.repo_id, - user_id=c.rhodecode_user.user_id, + repo=c.rhodecode_db_repo.repo_id, + user=c.rhodecode_user.user_id, revision=revision, f_path=request.POST.get('f_path'), - line_no=request.POST.get('line') + line_no=request.POST.get('line'), + status_change=(ChangesetStatus.get_status_lbl(status) + if status and change_status else None) ) - Session.commit() + + # get status if set ! + if status and change_status: + # if latest status was from pull request and it's closed + # disallow changing status ! + # dont_allow_on_closed_pull_request = True ! + + try: + ChangesetStatusModel().set_status( + c.rhodecode_db_repo.repo_id, + status, + c.rhodecode_user.user_id, + comm, + revision=revision, + dont_allow_on_closed_pull_request=True + ) + except StatusChangeOnClosedPullRequestError: + log.error(traceback.format_exc()) + 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, + revision=revision)) + action_logger(self.rhodecode_user, + 'user_commented_revision:%s' % revision, + c.rhodecode_db_repo, self.ip_addr, self.sa) + + Session().commit() + if not request.environ.get('HTTP_X_PARTIAL_XHR'): return redirect(h.url('changeset_home', repo_name=repo_name, revision=revision)) @@ -391,7 +437,7 @@ class ChangesetController(BaseRepoContro owner = lambda: co.author.user_id == c.rhodecode_user.user_id if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner: ChangesetCommentsModel().delete(comment=co) - Session.commit() + Session().commit() return True else: raise HTTPForbidden() diff --git a/rhodecode/controllers/compare.py b/rhodecode/controllers/compare.py new file mode 100644 --- /dev/null +++ b/rhodecode/controllers/compare.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.controllers.compare + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + compare controller for pylons showoing differences between two + repos, branches, bookmarks or tips + + :created_on: May 6, 2012 + :author: marcink + :copyright: (C) 2010-2012 Marcin Kuzminski + :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 . +import logging +import traceback + +from webob.exc import HTTPNotFound +from pylons import request, response, session, tmpl_context as c, url +from pylons.controllers.util import abort, redirect +from pylons.i18n.translation import _ + +from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError +from rhodecode.lib import helpers as h +from rhodecode.lib.base import BaseRepoController, render +from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator +from rhodecode.lib import diffs + +from rhodecode.model.db import Repository +from rhodecode.model.pull_request import PullRequestModel + +log = logging.getLogger(__name__) + + +class CompareController(BaseRepoController): + + @LoginRequired() + @HasRepoPermissionAnyDecorator('repository.read', 'repository.write', + 'repository.admin') + def __before__(self): + super(CompareController, self).__before__() + + def __get_cs_or_redirect(self, rev, repo, redirect_after=True): + """ + Safe way to get changeset if error occur it redirects to changeset with + proper message + + :param rev: revision to fetch + :param repo: repo instance + """ + + try: + type_, rev = rev + return repo.scm_instance.get_changeset(rev) + except EmptyRepositoryError, e: + if not redirect_after: + return None + h.flash(h.literal(_('There are no changesets yet')), + category='warning') + redirect(url('summary_home', repo_name=repo.repo_name)) + + except RepositoryError, e: + log.error(traceback.format_exc()) + h.flash(str(e), category='warning') + redirect(h.url('summary_home', repo_name=repo.repo_name)) + + def index(self, org_ref_type, org_ref, other_ref_type, other_ref): + + org_repo = c.rhodecode_db_repo.repo_name + org_ref = (org_ref_type, org_ref) + other_ref = (other_ref_type, other_ref) + other_repo = request.GET.get('repo', org_repo) + + c.swap_url = h.url('compare_url', repo_name=other_repo, + org_ref_type=other_ref[0], org_ref=other_ref[1], + other_ref_type=org_ref[0], other_ref=org_ref[1], + repo=org_repo) + + 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)) + raise HTTPNotFound + + if c.org_repo.scm_instance.alias != 'hg': + log.error('Review not available for GIT REPOS') + raise HTTPNotFound + + self.__get_cs_or_redirect(rev=org_ref, repo=org_repo) + self.__get_cs_or_redirect(rev=other_ref, repo=other_repo) + + c.cs_ranges, discovery_data = PullRequestModel().get_compare_data( + org_repo, org_ref, other_repo, other_ref + ) + + c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in + c.cs_ranges]) + c.target_repo = c.repo_name + # defines that we need hidden inputs with changesets + c.as_form = request.GET.get('as_form', False) + if request.environ.get('HTTP_X_PARTIAL_XHR'): + return render('compare/compare_cs.html') + + c.org_ref = org_ref[1] + c.other_ref = other_ref[1] + # diff needs to have swapped org with other to generate proper diff + _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref, + discovery_data) + diff_processor = diffs.DiffProcessor(_diff, format='gitdiff') + _parsed = diff_processor.prepare() + + c.files = [] + c.changes = {} + + for f in _parsed: + fid = h.FID('', f['filename']) + c.files.append([fid, f['operation'], f['filename'], f['stats']]) + diff = diff_processor.as_html(enable_comments=False, diff_lines=[f]) + c.changes[fid] = [f['operation'], f['filename'], diff] + + return render('compare/compare_diff.html') diff --git a/rhodecode/controllers/feed.py b/rhodecode/controllers/feed.py --- a/rhodecode/controllers/feed.py +++ b/rhodecode/controllers/feed.py @@ -28,11 +28,12 @@ import logging from pylons import url, response, tmpl_context as c from pylons.i18n.translation import _ -from rhodecode.lib.utils2 import safe_unicode +from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed + +from rhodecode.lib import helpers as h from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator from rhodecode.lib.base import BaseRepoController - -from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed +from rhodecode.lib.diffs import DiffProcessor log = logging.getLogger(__name__) @@ -49,31 +50,36 @@ class FeedController(BaseRepoController) self.title = self.title = _('%s %s feed') % (c.rhodecode_name, '%s') self.language = 'en-us' self.ttl = "5" - self.feed_nr = 10 + self.feed_nr = 20 def _get_title(self, cs): - return "R%s:%s - %s" % ( - cs.revision, cs.short_id, cs.message + return "%s" % ( + h.shorter(cs.message, 160) ) def __changes(self, cs): changes = [] - a = [safe_unicode(n.path) for n in cs.added] - if a: - changes.append('\nA ' + '\nA '.join(a)) - - m = [safe_unicode(n.path) for n in cs.changed] - if m: - changes.append('\nM ' + '\nM '.join(m)) + diffprocessor = DiffProcessor(cs.diff()) + stats = diffprocessor.prepare(inline_diff=False) + for st in stats: + st.update({'added': st['stats'][0], + 'removed': st['stats'][1]}) + changes.append('\n %(operation)s %(filename)s ' + '(%(added)s lines added, %(removed)s lines removed)' + % st) + return changes - d = [safe_unicode(n.path) for n in cs.removed] - if d: - changes.append('\nD ' + '\nD '.join(d)) - - changes.append('') - - return ''.join(changes) + def __get_desc(self, cs): + desc_msg = [] + desc_msg.append('%s %s %s:
' % (cs.author, _('commited on'), + h.fmt_date(cs.date))) + desc_msg.append('
')
+        desc_msg.append(cs.message)
+        desc_msg.append('\n')
+        desc_msg.extend(self.__changes(cs))
+        desc_msg.append('
') + return desc_msg def atom(self, repo_name): """Produce an atom-1.0 feed via feedgenerator module""" @@ -87,15 +93,13 @@ class FeedController(BaseRepoController) ) for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])): - desc_msg = [] - desc_msg.append('%s - %s
' % (cs.author, cs.date))
-            desc_msg.append(self.__changes(cs))
-
             feed.add_item(title=self._get_title(cs),
                           link=url('changeset_home', repo_name=repo_name,
                                    revision=cs.raw_id, qualified=True),
                           author_name=cs.author,
-                          description=''.join(desc_msg))
+                          description=''.join(self.__get_desc(cs)),
+                          pubdate=cs.date,
+                          )
 
         response.content_type = feed.mime_type
         return feed.writeString('utf-8')
@@ -112,15 +116,12 @@ class FeedController(BaseRepoController)
         )
 
         for cs in reversed(list(c.rhodecode_repo[-self.feed_nr:])):
-            desc_msg = []
-            desc_msg.append('%s - %s
' % (cs.author, cs.date))
-            desc_msg.append(self.__changes(cs))
-
             feed.add_item(title=self._get_title(cs),
                           link=url('changeset_home', repo_name=repo_name,
                                    revision=cs.raw_id, qualified=True),
                           author_name=cs.author,
-                          description=''.join(desc_msg),
+                          description=''.join(self.__get_desc(cs)),
+                          pubdate=cs.date,
                          )
 
         response.content_type = feed.mime_type
diff --git a/rhodecode/controllers/files.py b/rhodecode/controllers/files.py
--- a/rhodecode/controllers/files.py
+++ b/rhodecode/controllers/files.py
@@ -32,7 +32,6 @@ from pylons import request, response, tm
 from pylons.i18n.translation import _
 from pylons.controllers.util import redirect
 from pylons.decorators import jsonify
-from paste.fileapp import FileApp, _FileIter
 
 from rhodecode.lib import diffs
 from rhodecode.lib import helpers as h
@@ -41,7 +40,7 @@ from rhodecode.lib.compat import Ordered
 from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.lib.utils import EmptyChangeset
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
 from rhodecode.lib.vcs.conf import settings
 from rhodecode.lib.vcs.exceptions import RepositoryError, \
     ChangesetDoesNotExistError, EmptyRepositoryError, \
@@ -61,7 +60,6 @@ log = logging.getLogger(__name__)
 
 class FilesController(BaseRepoController):
 
-    @LoginRequired()
     def __before__(self):
         super(FilesController, self).__before__()
         c.cut_off_limit = self.cut_off_limit
@@ -83,8 +81,8 @@ class FilesController(BaseRepoController
             url_ = url('files_add_home',
                        repo_name=c.repo_name,
                        revision=0, f_path='')
-            add_new = '[%s]' % (url_, _('add new'))
-            h.flash(h.literal(_('There are no files yet %s' % add_new)),
+            add_new = '[%s]' % (url_, _('click here to add new file'))
+            h.flash(h.literal(_('There are no files yet %s') % add_new),
                     category='warning')
             redirect(h.url('summary_home', repo_name=repo_name))
 
@@ -113,6 +111,7 @@ class FilesController(BaseRepoController
 
         return file_node
 
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def index(self, repo_name, revision, f_path, annotate=False):
@@ -154,16 +153,25 @@ class FilesController(BaseRepoController
             c.file = c.changeset.get_node(f_path)
 
             if c.file.is_file():
-                c.file_history = self._get_node_history(c.changeset, f_path)
+                _hist = c.changeset.get_file_history(f_path)
+                c.file_history = self._get_node_history(c.changeset, f_path,
+                                                        _hist)
+                c.authors = []
+                for a in set([x.author for x in _hist]):
+                    c.authors.append((h.email(a), h.person(a)))
             else:
-                c.file_history = []
+                c.authors = c.file_history = []
         except RepositoryError, e:
             h.flash(str(e), category='warning')
             redirect(h.url('files_home', repo_name=repo_name,
-                           revision=revision))
+                           revision='tip'))
+
+        if request.environ.get('HTTP_X_PARTIAL_XHR'):
+            return render('files/files_ypjax.html')
 
         return render('files/files.html')
 
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def rawfile(self, repo_name, revision, f_path):
@@ -176,6 +184,7 @@ class FilesController(BaseRepoController
         response.content_type = file_node.mimetype
         return file_node.content
 
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def raw(self, repo_name, revision, f_path):
@@ -222,8 +231,18 @@ class FilesController(BaseRepoController
         response.content_type = mimetype
         return file_node.content
 
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
     def edit(self, repo_name, revision, f_path):
+        repo = Repository.get_by_repo_name(repo_name)
+        if repo.enable_locking and repo.locked[0]:
+            h.flash(_('This repository is has been locked by %s on %s')
+                % (h.person_by_id(repo.locked[0]),
+                   h.fmt_date(h.time_to_datetime(repo.locked[1]))),
+                  'warning')
+            return redirect(h.url('files_home',
+                                  repo_name=repo_name, revision='tip'))
+
         r_post = request.POST
 
         c.cs = self.__get_cs_or_redirect(revision, repo_name)
@@ -260,7 +279,7 @@ class FilesController(BaseRepoController
                                              user=self.rhodecode_user,
                                              author=author, message=message,
                                              content=content, f_path=f_path)
-                h.flash(_('Successfully committed to %s' % f_path),
+                h.flash(_('Successfully committed to %s') % f_path,
                         category='success')
 
             except Exception:
@@ -271,8 +290,19 @@ class FilesController(BaseRepoController
 
         return render('files/files_edit.html')
 
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
     def add(self, repo_name, revision, f_path):
+
+        repo = Repository.get_by_repo_name(repo_name)
+        if repo.enable_locking and repo.locked[0]:
+            h.flash(_('This repository is has been locked by %s on %s')
+                % (h.person_by_id(repo.locked[0]),
+                   h.fmt_date(h.time_to_datetime(repo.locked[1]))),
+                  'warning')
+            return redirect(h.url('files_home',
+                                  repo_name=repo_name, revision='tip'))
+
         r_post = request.POST
         c.cs = self.__get_cs_or_redirect(revision, repo_name,
                                          redirect_after=False)
@@ -313,7 +343,7 @@ class FilesController(BaseRepoController
                                            user=self.rhodecode_user,
                                            author=author, message=message,
                                            content=content, f_path=node_path)
-                h.flash(_('Successfully committed to %s' % node_path),
+                h.flash(_('Successfully committed to %s') % node_path,
                         category='success')
             except NodeAlreadyExistsError, e:
                 h.flash(_(e), category='error')
@@ -325,6 +355,7 @@ class FilesController(BaseRepoController
 
         return render('files/files_add.html')
 
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def archivefile(self, repo_name, fname):
@@ -361,27 +392,28 @@ class FilesController(BaseRepoController
         except (ImproperArchiveTypeError, KeyError):
             return _('Unknown archive type')
 
-        fd, _archive_name = tempfile.mkstemp(suffix='rcarchive')
-        with open(_archive_name, 'wb') as f:
-            cs.fill_archive(stream=f, kind=fileformat, subrepos=subrepos)
-
-        content_disposition = 'attachment; filename=%s-%s%s' \
-            % (repo_name, revision[:12], ext)
-        content_length = os.path.getsize(_archive_name)
+        fd, archive = tempfile.mkstemp()
+        t = open(archive, 'wb')
+        cs.fill_archive(stream=t, kind=fileformat, subrepos=subrepos)
+        t.close()
 
-        headers = [('Content-Disposition', str(content_disposition)),
-                   ('Content-Type', str(content_type)),
-                   ('Content-Length', str(content_length))]
+        def get_chunked_archive(archive):
+            stream = open(archive, 'rb')
+            while True:
+                data = stream.read(16 * 1024)
+                if not data:
+                    stream.close()
+                    os.close(fd)
+                    os.remove(archive)
+                    break
+                yield data
 
-        class _DestroyingFileWrapper(_FileIter):
-            def close(self):
-                self.file.close
-                os.remove(self.file.name)
+        response.content_disposition = str('attachment; filename=%s-%s%s' \
+                                           % (repo_name, revision[:12], ext))
+        response.content_type = str(content_type)
+        return get_chunked_archive(archive)
 
-        request.environ['wsgi.file_wrapper'] = _DestroyingFileWrapper
-        fapp = FileApp(_archive_name, headers=headers)
-        return fapp(request.environ, self.start_response)
-
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def diff(self, repo_name, f_path):
@@ -454,8 +486,9 @@ class FilesController(BaseRepoController
 
         return render('files/file_diff.html')
 
-    def _get_node_history(self, cs, f_path):
-        changesets = cs.get_file_history(f_path)
+    def _get_node_history(self, cs, f_path, changesets=None):
+        if changesets is None:
+            changesets = cs.get_file_history(f_path)
         hist_l = []
 
         changesets_group = ([], _("Changesets"))
@@ -479,12 +512,13 @@ class FilesController(BaseRepoController
 
         return hist_l
 
-    @jsonify
+    @LoginRequired()
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
+    @jsonify
     def nodelist(self, repo_name, revision, f_path):
         if request.environ.get('HTTP_X_PARTIAL_XHR'):
             cs = self.__get_cs_or_redirect(revision, repo_name)
             _d, _f = ScmModel().get_nodes(repo_name, cs.raw_id, f_path,
                                           flat=False)
-            return _d + _f
+            return {'nodes': _d + _f}
diff --git a/rhodecode/controllers/forks.py b/rhodecode/controllers/forks.py
--- a/rhodecode/controllers/forks.py
+++ b/rhodecode/controllers/forks.py
@@ -35,11 +35,13 @@ import rhodecode.lib.helpers as h
 
 from rhodecode.lib.helpers import Page
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
-    NotAnonymous, HasRepoPermissionAny
+    NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
+    HasPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.forms import RepoForkForm
+from rhodecode.model.scm import ScmModel
 
 log = logging.getLogger(__name__)
 
@@ -53,6 +55,8 @@ class ForksController(BaseRepoController
     def __load_defaults(self):
         c.repo_groups = RepoGroup.groups_choices()
         c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
+        choices, c.landing_revs = ScmModel().get_repo_landing_revs()
+        c.landing_revs_choices = choices
 
     def __load_data(self, repo_name=None):
         """
@@ -120,6 +124,7 @@ class ForksController(BaseRepoController
         return render('/forks/forks.html')
 
     @NotAnonymous()
+    @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def fork(self, repo_name):
@@ -142,24 +147,23 @@ class ForksController(BaseRepoController
             force_defaults=False
         )
 
-
     @NotAnonymous()
+    @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
     @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
                                    'repository.admin')
     def fork_create(self, repo_name):
         self.__load_defaults()
         c.repo_info = Repository.get_by_repo_name(repo_name)
         _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
-                             repo_groups=c.repo_groups_choices,)()
+                             repo_groups=c.repo_groups_choices,
+                             landing_revs=c.landing_revs_choices)()
         form_result = {}
         try:
             form_result = _form.to_python(dict(request.POST))
-            # add org_path of repo so we can do a clone from it later
-            form_result['org_path'] = c.repo_info.repo_name
 
             # create fork is done sometimes async on celery, db transaction
             # management is handled there.
-            RepoModel().create_fork(form_result, self.rhodecode_user)
+            RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
             h.flash(_('forked %s repository as %s') \
                       % (repo_name, form_result['repo_name']),
                     category='success')
diff --git a/rhodecode/controllers/home.py b/rhodecode/controllers/home.py
--- a/rhodecode/controllers/home.py
+++ b/rhodecode/controllers/home.py
@@ -26,7 +26,7 @@
 import logging
 
 from pylons import tmpl_context as c, request
-from paste.httpexceptions import HTTPBadRequest
+from webob.exc import HTTPBadRequest
 
 from rhodecode.lib.auth import LoginRequired
 from rhodecode.lib.base import BaseController, render
@@ -51,7 +51,8 @@ class HomeController(BaseController):
         if request.is_xhr:
             all_repos = Repository.query().order_by(Repository.repo_name).all()
             c.repos_list = self.scm_model.get_repos(all_repos,
-                                                    sort_key='name_sort')
+                                                    sort_key='name_sort',
+                                                    simple=True)
             return render('/repo_switcher_list.html')
         else:
             return HTTPBadRequest()
diff --git a/rhodecode/controllers/journal.py b/rhodecode/controllers/journal.py
--- a/rhodecode/controllers/journal.py
+++ b/rhodecode/controllers/journal.py
@@ -30,7 +30,7 @@ from sqlalchemy.orm import joinedload
 from webhelpers.paginate import Page
 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
 
-from paste.httpexceptions import HTTPBadRequest
+from webob.exc import HTTPBadRequest
 from pylons import request, tmpl_context as c, response, url
 from pylons.i18n.translation import _
 
@@ -49,8 +49,6 @@ class JournalController(BaseController):
 
     def __before__(self):
         super(JournalController, self).__before__()
-        self.rhodecode_user = self.rhodecode_user
-        self.title = _('%s public journal %s feed') % (c.rhodecode_name, '%s')
         self.language = 'en-us'
         self.ttl = "5"
         self.feed_nr = 20
@@ -84,6 +82,30 @@ class JournalController(BaseController):
             return c.journal_data
         return render('journal/journal.html')
 
+    @LoginRequired(api_access=True)
+    @NotAnonymous()
+    def journal_atom(self):
+        """
+        Produce an atom-1.0 feed via feedgenerator module
+        """
+        following = self.sa.query(UserFollowing)\
+            .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
+            .options(joinedload(UserFollowing.follows_repository))\
+            .all()
+        return self._atom_feed(following, public=False)
+
+    @LoginRequired(api_access=True)
+    @NotAnonymous()
+    def journal_rss(self):
+        """
+        Produce an rss feed via feedgenerator module
+        """
+        following = self.sa.query(UserFollowing)\
+            .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
+            .options(joinedload(UserFollowing.follows_repository))\
+            .all()
+        return self._rss_feed(following, public=False)
+
     def _get_daily_aggregate(self, journal):
         groups = []
         for k, g in groupby(journal, lambda x: x.action_as_day):
@@ -173,6 +195,80 @@ class JournalController(BaseController):
             return c.journal_data
         return render('journal/public_journal.html')
 
+    def _atom_feed(self, repos, public=True):
+        journal = self._get_journal_data(repos)
+        if public:
+            _link = url('public_journal_atom', qualified=True)
+            _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
+                                  'atom feed')
+        else:
+            _link = url('journal_atom', qualified=True)
+            _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
+
+        feed = Atom1Feed(title=_desc,
+                         link=_link,
+                         description=_desc,
+                         language=self.language,
+                         ttl=self.ttl)
+
+        for entry in journal[:self.feed_nr]:
+            action, action_extra, ico = h.action_parser(entry, feed=True)
+            title = "%s - %s %s" % (entry.user.short_contact, action(),
+                                 entry.repository.repo_name)
+            desc = action_extra()
+            _url = None
+            if entry.repository is not None:
+                _url = url('changelog_home',
+                           repo_name=entry.repository.repo_name,
+                           qualified=True)
+
+            feed.add_item(title=title,
+                          pubdate=entry.action_date,
+                          link=_url or url('', qualified=True),
+                          author_email=entry.user.email,
+                          author_name=entry.user.full_contact,
+                          description=desc)
+
+        response.content_type = feed.mime_type
+        return feed.writeString('utf-8')
+
+    def _rss_feed(self, repos, public=True):
+        journal = self._get_journal_data(repos)
+        if public:
+            _link = url('public_journal_atom', qualified=True)
+            _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
+                                  'rss feed')
+        else:
+            _link = url('journal_atom', qualified=True)
+            _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
+
+        feed = Rss201rev2Feed(title=_desc,
+                         link=_link,
+                         description=_desc,
+                         language=self.language,
+                         ttl=self.ttl)
+
+        for entry in journal[:self.feed_nr]:
+            action, action_extra, ico = h.action_parser(entry, feed=True)
+            title = "%s - %s %s" % (entry.user.short_contact, action(),
+                                 entry.repository.repo_name)
+            desc = action_extra()
+            _url = None
+            if entry.repository is not None:
+                _url = url('changelog_home',
+                           repo_name=entry.repository.repo_name,
+                           qualified=True)
+
+            feed.add_item(title=title,
+                          pubdate=entry.action_date,
+                          link=_url or url('', qualified=True),
+                          author_email=entry.user.email,
+                          author_name=entry.user.full_contact,
+                          description=desc)
+
+        response.content_type = feed.mime_type
+        return feed.writeString('utf-8')
+
     @LoginRequired(api_access=True)
     def public_journal_atom(self):
         """
@@ -183,29 +279,7 @@ class JournalController(BaseController):
             .options(joinedload(UserFollowing.follows_repository))\
             .all()
 
-        journal = self._get_journal_data(c.following)
-
-        feed = Atom1Feed(title=self.title % 'atom',
-                         link=url('public_journal_atom', qualified=True),
-                         description=_('Public journal'),
-                         language=self.language,
-                         ttl=self.ttl)
-
-        for entry in journal[:self.feed_nr]:
-            #tmpl = h.action_parser(entry)[0]
-            action, action_extra = h.action_parser(entry, feed=True)
-            title = "%s - %s %s" % (entry.user.short_contact, action,
-                                 entry.repository.repo_name)
-            desc = action_extra()
-            feed.add_item(title=title,
-                          pubdate=entry.action_date,
-                          link=url('', qualified=True),
-                          author_email=entry.user.email,
-                          author_name=entry.user.full_contact,
-                          description=desc)
-
-        response.content_type = feed.mime_type
-        return feed.writeString('utf-8')
+        return self._atom_feed(c.following)
 
     @LoginRequired(api_access=True)
     def public_journal_rss(self):
@@ -217,26 +291,4 @@ class JournalController(BaseController):
             .options(joinedload(UserFollowing.follows_repository))\
             .all()
 
-        journal = self._get_journal_data(c.following)
-
-        feed = Rss201rev2Feed(title=self.title % 'rss',
-                         link=url('public_journal_rss', qualified=True),
-                         description=_('Public journal'),
-                         language=self.language,
-                         ttl=self.ttl)
-
-        for entry in journal[:self.feed_nr]:
-            #tmpl = h.action_parser(entry)[0]
-            action, action_extra = h.action_parser(entry, feed=True)
-            title = "%s - %s %s" % (entry.user.short_contact, action,
-                                 entry.repository.repo_name)
-            desc = action_extra()
-            feed.add_item(title=title,
-                          pubdate=entry.action_date,
-                          link=url('', qualified=True),
-                          author_email=entry.user.email,
-                          author_name=entry.user.full_contact,
-                          description=desc)
-
-        response.content_type = feed.mime_type
-        return feed.writeString('utf-8')
+        return self._rss_feed(c.following)
diff --git a/rhodecode/controllers/login.py b/rhodecode/controllers/login.py
--- a/rhodecode/controllers/login.py
+++ b/rhodecode/controllers/login.py
@@ -25,9 +25,11 @@
 
 import logging
 import formencode
+import datetime
+import urlparse
 
 from formencode import htmlfill
-
+from webob.exc import HTTPFound
 from pylons.i18n.translation import _
 from pylons.controllers.util import abort, redirect
 from pylons import request, response, session, tmpl_context as c, url
@@ -51,7 +53,7 @@ class LoginController(BaseController):
 
     def index(self):
         # redirect if already logged in
-        c.came_from = request.GET.get('came_from', None)
+        c.came_from = request.GET.get('came_from')
 
         if self.rhodecode_user.is_authenticated \
                             and self.rhodecode_user.username != 'default':
@@ -62,6 +64,7 @@ class LoginController(BaseController):
             # import Login Form validator class
             login_form = LoginForm()
             try:
+                session.invalidate()
                 c.form_result = login_form.to_python(dict(request.POST))
                 # form checks for username/password, now we're authenticated
                 username = c.form_result['username']
@@ -70,22 +73,46 @@ class LoginController(BaseController):
                 auth_user.set_authenticated()
                 cs = auth_user.get_cookie_store()
                 session['rhodecode_user'] = cs
+                user.update_lastlogin()
+                Session().commit()
+
                 # If they want to be remembered, update the cookie
                 if c.form_result['remember'] is not False:
-                    session.cookie_expires = False
-                session._set_cookie_values()
-                session._update_cookie_out()
+                    _year = (datetime.datetime.now() +
+                             datetime.timedelta(seconds=60 * 60 * 24 * 365))
+                    session._set_cookie_expires(_year)
+
                 session.save()
 
                 log.info('user %s is now authenticated and stored in '
                          'session, session attrs %s' % (username, cs))
-                user.update_lastlogin()
-                Session.commit()
+
+                # dumps session attrs back to cookie
+                session._update_cookie_out()
 
+                # we set new cookie
+                headers = None
+                if session.request['set_cookie']:
+                    # send set-cookie headers back to response to update cookie
+                    headers = [('Set-Cookie', session.request['cookie_out'])]
+
+                allowed_schemes = ['http', 'https']
                 if c.came_from:
-                    return redirect(c.came_from)
+                    parsed = urlparse.urlparse(c.came_from)
+                    server_parsed = urlparse.urlparse(url.current())
+                    if parsed.scheme and parsed.scheme not in allowed_schemes:
+                        log.error(
+                            'Suspicious URL scheme detected %s for url %s' %
+                            (parsed.scheme, parsed))
+                        c.came_from = url('home')
+                    elif server_parsed.netloc != parsed.netloc:
+                        log.error('Suspicious NETLOC detected %s for url %s'
+                                  'server url is: %s' %
+                                  (parsed.netloc, parsed, server_parsed))
+                        c.came_from = url('home')
+                    raise HTTPFound(location=c.came_from, headers=headers)
                 else:
-                    return redirect(url('home'))
+                    raise HTTPFound(location=url('home'), headers=headers)
 
             except formencode.Invalid, errors:
                 return htmlfill.render(
@@ -115,7 +142,7 @@ class LoginController(BaseController):
                 UserModel().create_registration(form_result)
                 h.flash(_('You have successfully registered into rhodecode'),
                             category='success')
-                Session.commit()
+                Session().commit()
                 return redirect(url('login_home'))
 
             except formencode.Invalid, errors:
diff --git a/rhodecode/controllers/pullrequests.py b/rhodecode/controllers/pullrequests.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/controllers/pullrequests.py
@@ -0,0 +1,404 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.controllers.pullrequests
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    pull requests controller for rhodecode for initializing pull requests
+
+    :created_on: May 7, 2012
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski 
+    :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 .
+import logging
+import traceback
+import formencode
+
+from webob.exc import HTTPNotFound, HTTPForbidden
+from collections import defaultdict
+from itertools import groupby
+
+from pylons import request, response, session, tmpl_context as c, url
+from pylons.controllers.util import abort, redirect
+from pylons.i18n.translation import _
+from pylons.decorators import jsonify
+
+from rhodecode.lib.compat import json
+from rhodecode.lib.base import BaseRepoController, render
+from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
+    NotAnonymous
+from rhodecode.lib import helpers as h
+from rhodecode.lib import diffs
+from rhodecode.lib.utils import action_logger
+from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
+    ChangesetComment
+from rhodecode.model.pull_request import PullRequestModel
+from rhodecode.model.meta import Session
+from rhodecode.model.repo import RepoModel
+from rhodecode.model.comment import ChangesetCommentsModel
+from rhodecode.model.changeset_status import ChangesetStatusModel
+from rhodecode.model.forms import PullRequestForm
+
+log = logging.getLogger(__name__)
+
+
+class PullrequestsController(BaseRepoController):
+
+    @LoginRequired()
+    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
+                                   'repository.admin')
+    def __before__(self):
+        super(PullrequestsController, self).__before__()
+        repo_model = RepoModel()
+        c.users_array = repo_model.get_users_js()
+        c.users_groups_array = repo_model.get_users_groups_js()
+
+    def _get_repo_refs(self, repo):
+        hist_l = []
+
+        branches_group = ([('branch:%s:%s' % (k, v), k) for
+                         k, v in repo.branches.iteritems()], _("Branches"))
+        bookmarks_group = ([('book:%s:%s' % (k, v), k) for
+                         k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
+        tags_group = ([('tag:%s:%s' % (k, v), k) for
+                         k, v in repo.tags.iteritems()], _("Tags"))
+
+        hist_l.append(bookmarks_group)
+        hist_l.append(branches_group)
+        hist_l.append(tags_group)
+
+        return hist_l
+
+    def show_all(self, repo_name):
+        c.pull_requests = PullRequestModel().get_all(repo_name)
+        c.repo_name = repo_name
+        return render('/pullrequests/pullrequest_show_all.html')
+
+    @NotAnonymous()
+    def index(self):
+        org_repo = c.rhodecode_db_repo
+
+        if org_repo.scm_instance.alias != 'hg':
+            log.error('Review not available for GIT REPOS')
+            raise HTTPNotFound
+
+        other_repos_info = {}
+
+        c.org_refs = self._get_repo_refs(c.rhodecode_repo)
+        c.org_repos = []
+        c.other_repos = []
+        c.org_repos.append((org_repo.repo_name, '%s/%s' % (
+                                org_repo.user.username, c.repo_name))
+                           )
+
+        # add org repo to other so we can open pull request agains itself
+        c.other_repos.extend(c.org_repos)
+
+        c.default_pull_request = org_repo.repo_name
+        c.default_revs = self._get_repo_refs(org_repo.scm_instance)
+        #add orginal repo
+        other_repos_info[org_repo.repo_name] = {
+            'gravatar': h.gravatar_url(org_repo.user.email, 24),
+            'description': org_repo.description,
+            'revs': h.select('other_ref', '', c.default_revs, class_='refs')
+        }
+
+        #gather forks and add to this list
+        for fork in org_repo.forks:
+            c.other_repos.append((fork.repo_name, '%s/%s' % (
+                                    fork.user.username, fork.repo_name))
+                                 )
+            other_repos_info[fork.repo_name] = {
+                'gravatar': h.gravatar_url(fork.user.email, 24),
+                'description': fork.description,
+                'revs': h.select('other_ref', '',
+                                 self._get_repo_refs(fork.scm_instance),
+                                 class_='refs')
+            }
+        #add parents of this fork also
+        if org_repo.parent:
+            c.default_pull_request = org_repo.parent.repo_name
+            c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
+                                        org_repo.parent.user.username,
+                                        org_repo.parent.repo_name))
+                                     )
+            other_repos_info[org_repo.parent.repo_name] = {
+                'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
+                'description': org_repo.parent.description,
+                'revs': h.select('other_ref', '',
+                                 self._get_repo_refs(org_repo.parent.scm_instance),
+                                 class_='refs')
+            }
+
+        c.other_repos_info = json.dumps(other_repos_info)
+        c.review_members = [org_repo.user]
+        return render('/pullrequests/pullrequest.html')
+
+    @NotAnonymous()
+    def create(self, repo_name):
+        try:
+            _form = PullRequestForm()().to_python(request.POST)
+        except formencode.Invalid, errors:
+            log.error(traceback.format_exc())
+            if errors.error_dict.get('revisions'):
+                msg = 'Revisions: %s' % errors.error_dict['revisions']
+            elif errors.error_dict.get('pullrequest_title'):
+                msg = _('Pull request requires a title with min. 3 chars')
+            else:
+                msg = _('error during creation of pull request')
+
+            h.flash(msg, 'error')
+            return redirect(url('pullrequest_home', repo_name=repo_name))
+
+        org_repo = _form['org_repo']
+        org_ref = _form['org_ref']
+        other_repo = _form['other_repo']
+        other_ref = _form['other_ref']
+        revisions = _form['revisions']
+        reviewers = _form['review_members']
+
+        title = _form['pullrequest_title']
+        description = _form['pullrequest_desc']
+
+        try:
+            pull_request = PullRequestModel().create(
+                self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
+                other_ref, revisions, reviewers, title, description
+            )
+            Session().commit()
+            h.flash(_('Successfully opened new pull request'),
+                    category='success')
+        except Exception:
+            h.flash(_('Error occurred during sending pull request'),
+                    category='error')
+            log.error(traceback.format_exc())
+            return redirect(url('pullrequest_home', repo_name=repo_name))
+
+        return redirect(url('pullrequest_show', repo_name=other_repo,
+                            pull_request_id=pull_request.pull_request_id))
+
+    @NotAnonymous()
+    @jsonify
+    def update(self, repo_name, pull_request_id):
+        pull_request = PullRequest.get_or_404(pull_request_id)
+        if pull_request.is_closed():
+            raise HTTPForbidden()
+        #only owner or admin can update it
+        owner = pull_request.author.user_id == c.rhodecode_user.user_id
+        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
+            reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
+                       request.POST.get('reviewers_ids', '').split(',')))
+
+            PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
+            Session.commit()
+            return True
+        raise HTTPForbidden()
+
+    @NotAnonymous()
+    @jsonify
+    def delete(self, repo_name, pull_request_id):
+        pull_request = PullRequest.get_or_404(pull_request_id)
+        #only owner can delete it !
+        if pull_request.author.user_id == c.rhodecode_user.user_id:
+            PullRequestModel().delete(pull_request)
+            Session().commit()
+            h.flash(_('Successfully deleted pull request'),
+                    category='success')
+            return redirect(url('admin_settings_my_account'))
+        raise HTTPForbidden()
+
+    def _load_compare_data(self, pull_request, enable_comments=True):
+        """
+        Load context data needed for generating compare diff
+
+        :param pull_request:
+        :type pull_request:
+        """
+
+        org_repo = pull_request.org_repo
+        (org_ref_type,
+         org_ref_name,
+         org_ref_rev) = pull_request.org_ref.split(':')
+
+        other_repo = pull_request.other_repo
+        (other_ref_type,
+         other_ref_name,
+         other_ref_rev) = pull_request.other_ref.split(':')
+
+        # despite opening revisions for bookmarks/branches/tags, we always
+        # convert this to rev to prevent changes after book or branch change
+        org_ref = ('rev', org_ref_rev)
+        other_ref = ('rev', other_ref_rev)
+
+        c.org_repo = org_repo
+        c.other_repo = other_repo
+
+        c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
+                                       org_repo, org_ref, other_repo, other_ref
+                                      )
+
+        c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
+                                                   c.cs_ranges])
+        # defines that we need hidden inputs with changesets
+        c.as_form = request.GET.get('as_form', False)
+
+        c.org_ref = org_ref[1]
+        c.other_ref = other_ref[1]
+        # diff needs to have swapped org with other to generate proper diff
+        _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
+                             discovery_data)
+        diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
+        _parsed = diff_processor.prepare()
+
+        c.files = []
+        c.changes = {}
+
+        for f in _parsed:
+            fid = h.FID('', f['filename'])
+            c.files.append([fid, f['operation'], f['filename'], f['stats']])
+            diff = diff_processor.as_html(enable_comments=enable_comments,
+                                          diff_lines=[f])
+            c.changes[fid] = [f['operation'], f['filename'], diff]
+
+    def show(self, repo_name, pull_request_id):
+        repo_model = RepoModel()
+        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)
+
+        cc_model = ChangesetCommentsModel()
+        cs_model = ChangesetStatusModel()
+        _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
+                                            pull_request=c.pull_request,
+                                            with_revisions=True)
+
+        cs_statuses = defaultdict(list)
+        for st in _cs_statuses:
+            cs_statuses[st.author.username] += [st]
+
+        c.pull_request_reviewers = []
+        c.pull_request_pending_reviewers = []
+        for o in c.pull_request.reviewers:
+            st = cs_statuses.get(o.user.username, None)
+            if st:
+                sorter = lambda k: k.version
+                st = [(x, list(y)[0])
+                      for x, y in (groupby(sorted(st, key=sorter), sorter))]
+            else:
+                c.pull_request_pending_reviewers.append(o.user)
+            c.pull_request_reviewers.append([o.user, st])
+
+        # pull_requests repo_name we opened it against
+        # ie. other_repo must match
+        if repo_name != c.pull_request.other_repo.repo_name:
+            raise HTTPNotFound
+
+        # load compare data into template context
+        enable_comments = not c.pull_request.is_closed()
+        self._load_compare_data(c.pull_request, enable_comments=enable_comments)
+
+        # inline comments
+        c.inline_cnt = 0
+        c.inline_comments = cc_model.get_inline_comments(
+                                c.rhodecode_db_repo.repo_id,
+                                pull_request=pull_request_id)
+        # count inline comments
+        for __, lines in c.inline_comments:
+            for comments in lines.values():
+                c.inline_cnt += len(comments)
+        # comments
+        c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
+                                           pull_request=pull_request_id)
+
+        # changeset(pull-request) status
+        c.current_changeset_status = cs_model.calculate_status(
+                                        c.pull_request_reviewers
+                                     )
+        c.changeset_statuses = ChangesetStatus.STATUSES
+        c.target_repo = c.pull_request.org_repo.repo_name
+        return render('/pullrequests/pullrequest_show.html')
+
+    @NotAnonymous()
+    @jsonify
+    def comment(self, repo_name, pull_request_id):
+        pull_request = PullRequest.get_or_404(pull_request_id)
+        if pull_request.is_closed():
+            raise HTTPForbidden()
+
+        status = request.POST.get('changeset_status')
+        change_status = request.POST.get('change_changeset_status')
+
+        comm = ChangesetCommentsModel().create(
+            text=request.POST.get('text'),
+            repo=c.rhodecode_db_repo.repo_id,
+            user=c.rhodecode_user.user_id,
+            pull_request=pull_request_id,
+            f_path=request.POST.get('f_path'),
+            line_no=request.POST.get('line'),
+            status_change=(ChangesetStatus.get_status_lbl(status)
+                           if status and change_status else None)
+        )
+
+        # get status if set !
+        if status and change_status:
+            ChangesetStatusModel().set_status(
+                c.rhodecode_db_repo.repo_id,
+                status,
+                c.rhodecode_user.user_id,
+                comm,
+                pull_request=pull_request_id
+            )
+        action_logger(self.rhodecode_user,
+                      'user_commented_pull_request:%s' % pull_request_id,
+                      c.rhodecode_db_repo, self.ip_addr, self.sa)
+
+        if request.POST.get('save_close'):
+            PullRequestModel().close_pull_request(pull_request_id)
+            action_logger(self.rhodecode_user,
+                      'user_closed_pull_request:%s' % pull_request_id,
+                      c.rhodecode_db_repo, self.ip_addr, self.sa)
+
+        Session().commit()
+
+        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
+            return redirect(h.url('pullrequest_show', repo_name=repo_name,
+                                  pull_request_id=pull_request_id))
+
+        data = {
+           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
+        }
+        if comm:
+            c.co = comm
+            data.update(comm.get_dict())
+            data.update({'rendered_text':
+                         render('changeset/changeset_comment_block.html')})
+
+        return data
+
+    @NotAnonymous()
+    @jsonify
+    def delete_comment(self, repo_name, comment_id):
+        co = ChangesetComment.get(comment_id)
+        if co.pull_request.is_closed():
+            #don't allow deleting comments on closed pull request
+            raise HTTPForbidden()
+
+        owner = lambda: co.author.user_id == c.rhodecode_user.user_id
+        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
+            ChangesetCommentsModel().delete(comment=co)
+            Session().commit()
+            return True
+        else:
+            raise HTTPForbidden()
diff --git a/rhodecode/controllers/search.py b/rhodecode/controllers/search.py
--- a/rhodecode/controllers/search.py
+++ b/rhodecode/controllers/search.py
@@ -3,7 +3,7 @@
     rhodecode.controllers.search
     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-    Search controller for rhodecode
+    Search controller for RhodeCode
 
     :created_on: Aug 7, 2010
     :author: marcink
@@ -30,7 +30,8 @@ from pylons import request, config, tmpl
 
 from rhodecode.lib.auth import LoginRequired
 from rhodecode.lib.base import BaseController, render
-from rhodecode.lib.indexers import SCHEMA, IDX_NAME, ResultWrapper
+from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
+    IDX_NAME, WhooshResultWrapper
 
 from webhelpers.paginate import Page
 from webhelpers.util import update_params
@@ -38,6 +39,7 @@ from webhelpers.util import update_param
 from whoosh.index import open_dir, EmptyIndexError
 from whoosh.qparser import QueryParser, QueryParserError
 from whoosh.query import Phrase, Wildcard, Term, Prefix
+from rhodecode.model.repo import RepoModel
 
 log = logging.getLogger(__name__)
 
@@ -53,25 +55,41 @@ class SearchController(BaseController):
         c.formated_results = []
         c.runtime = ''
         c.cur_query = request.GET.get('q', None)
-        c.cur_type = request.GET.get('type', 'source')
+        c.cur_type = request.GET.get('type', 'content')
         c.cur_search = search_type = {'content': 'content',
-                                      'commit': 'content',
+                                      'commit': 'message',
                                       'path': 'path',
-                                      'repository': 'repository'}\
-                                      .get(c.cur_type, 'content')
+                                      'repository': 'repository'
+                                      }.get(c.cur_type, 'content')
+
+        index_name = {
+            'content': IDX_NAME,
+            'commit': CHGSET_IDX_NAME,
+            'path': IDX_NAME
+        }.get(c.cur_type, IDX_NAME)
+
+        schema_defn = {
+            'content': SCHEMA,
+            'commit': CHGSETS_SCHEMA,
+            'path': SCHEMA
+        }.get(c.cur_type, SCHEMA)
+
+        log.debug('IDX: %s' % index_name)
+        log.debug('SCHEMA: %s' % schema_defn)
 
         if c.cur_query:
             cur_query = c.cur_query.lower()
+            log.debug(cur_query)
 
         if c.cur_query:
             p = int(request.params.get('page', 1))
             highlight_items = set()
             try:
                 idx = open_dir(config['app_conf']['index_dir'],
-                               indexname=IDX_NAME)
+                               indexname=index_name)
                 searcher = idx.searcher()
 
-                qp = QueryParser(search_type, schema=SCHEMA)
+                qp = QueryParser(search_type, schema=schema_defn)
                 if c.repo_name:
                     cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
                 try:
@@ -83,13 +101,13 @@ class SearchController(BaseController):
                         highlight_items.add(query.text)
                     else:
                         for i in query.all_terms():
-                            if i[0] == 'content':
+                            if i[0] in ['content', 'message']:
                                 highlight_items.add(i[1])
 
                     matcher = query.matcher(searcher)
 
-                    log.debug(query)
-                    log.debug(highlight_items)
+                    log.debug('query: %s' % query)
+                    log.debug('hl terms: %s' % highlight_items)
                     results = searcher.search(query)
                     res_ln = len(results)
                     c.runtime = '%s results (%.3f seconds)' % (
@@ -98,11 +116,11 @@ class SearchController(BaseController):
 
                     def url_generator(**kw):
                         return update_params("?q=%s&type=%s" \
-                                           % (c.cur_query, c.cur_search), **kw)
-
+                                           % (c.cur_query, c.cur_type), **kw)
+                    repo_location = RepoModel().repos_path
                     c.formated_results = Page(
-                        ResultWrapper(search_type, searcher, matcher,
-                                      highlight_items),
+                        WhooshResultWrapper(search_type, searcher, matcher,
+                                            highlight_items, repo_location),
                         page=p,
                         item_count=res_ln,
                         items_per_page=10,
@@ -121,6 +139,5 @@ class SearchController(BaseController):
                 log.error(traceback.format_exc())
                 c.runtime = _('An error occurred during this search operation')
 
-
         # Return a rendered template
         return render('/search/search.html')
diff --git a/rhodecode/controllers/settings.py b/rhodecode/controllers/settings.py
--- a/rhodecode/controllers/settings.py
+++ b/rhodecode/controllers/settings.py
@@ -43,6 +43,7 @@ from rhodecode.model.forms import RepoSe
 from rhodecode.model.repo import RepoModel
 from rhodecode.model.db import RepoGroup
 from rhodecode.model.meta import Session
+from rhodecode.model.scm import ScmModel
 
 log = logging.getLogger(__name__)
 
@@ -60,6 +61,8 @@ class SettingsController(BaseRepoControl
         repo_model = RepoModel()
         c.users_array = repo_model.get_users_js()
         c.users_groups_array = repo_model.get_users_groups_js()
+        choices, c.landing_revs = ScmModel().get_repo_landing_revs()
+        c.landing_revs_choices = choices
 
     @HasRepoPermissionAllDecorator('repository.admin')
     def index(self, repo_name):
@@ -94,17 +97,18 @@ class SettingsController(BaseRepoControl
 
         _form = RepoSettingsForm(edit=True,
                                  old_data={'repo_name': repo_name},
-                                 repo_groups=c.repo_groups_choices)()
+                                 repo_groups=c.repo_groups_choices,
+                                 landing_revs=c.landing_revs_choices)()
         try:
             form_result = _form.to_python(dict(request.POST))
 
             repo_model.update(repo_name, form_result)
             invalidate_cache('get_repo_cached_%s' % repo_name)
-            h.flash(_('Repository %s updated successfully' % repo_name),
+            h.flash(_('Repository %s updated successfully') % repo_name,
                     category='success')
             changed_name = form_result['repo_name_full']
             action_logger(self.rhodecode_user, 'user_updated_repo',
-                          changed_name, '', self.sa)
+                          changed_name, self.ip_addr, self.sa)
             Session.commit()
         except formencode.Invalid, errors:
             c.repo_info = repo_model.get_by_repo_name(repo_name)
@@ -145,7 +149,7 @@ class SettingsController(BaseRepoControl
             return redirect(url('home'))
         try:
             action_logger(self.rhodecode_user, 'user_deleted_repo',
-                              repo_name, '', self.sa)
+                              repo_name, self.ip_addr, self.sa)
             repo_model.delete(repo)
             invalidate_cache('get_repo_cached_%s' % repo_name)
             h.flash(_('deleted repository %s') % repo_name, category='success')
diff --git a/rhodecode/controllers/summary.py b/rhodecode/controllers/summary.py
--- a/rhodecode/controllers/summary.py
+++ b/rhodecode/controllers/summary.py
@@ -45,12 +45,13 @@ from rhodecode.model.db import Statistic
 from rhodecode.lib.utils2 import safe_unicode
 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 from rhodecode.lib.base import BaseRepoController, render
-from rhodecode.lib.utils import EmptyChangeset
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
 from rhodecode.lib.markup_renderer import MarkupRenderer
 from rhodecode.lib.celerylib import run_task
 from rhodecode.lib.celerylib.tasks import get_commits_stats
 from rhodecode.lib.helpers import RepoPage
 from rhodecode.lib.compat import json, OrderedDict
+from rhodecode.lib.vcs.nodes import FileNode
 
 log = logging.getLogger(__name__)
 
@@ -179,27 +180,31 @@ class SummaryController(BaseRepoControll
         if c.enable_downloads:
             c.download_options = self._get_download_links(c.rhodecode_repo)
 
-        c.readme_data, c.readme_file = self.__get_readme_data(
-            c.rhodecode_db_repo.repo_name, c.rhodecode_repo
-        )
+        c.readme_data, c.readme_file = \
+            self.__get_readme_data(c.rhodecode_db_repo)
         return render('summary/summary.html')
 
-    def __get_readme_data(self, repo_name, repo):
+    def __get_readme_data(self, db_repo):
+        repo_name = db_repo.repo_name
 
         @cache_region('long_term')
         def _get_readme_from_cache(key):
             readme_data = None
             readme_file = None
-            log.debug('Fetching readme file')
+            log.debug('Looking for README file')
             try:
-                cs = repo.get_changeset()  # fetches TIP
+                # get's the landing revision! or tip if fails
+                cs = db_repo.get_landing_changeset()
                 renderer = MarkupRenderer()
                 for f in README_FILES:
                     try:
                         readme = cs.get_node(f)
+                        if not isinstance(readme, FileNode):
+                            continue
                         readme_file = f
+                        log.debug('Found README file `%s` rendering...' %
+                                  readme_file)
                         readme_data = renderer.render(readme.content, f)
-                        log.debug('Found readme %s' % readme_file)
                         break
                     except NodeDoesNotExistError:
                         continue
diff --git a/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po
--- a/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po
+++ b/rhodecode/i18n/en/LC_MESSAGES/rhodecode.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: rhodecode 0.1\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-09-14 15:50-0300\n"
+"POT-Creation-Date: 2012-09-02 20:30+0200\n"
 "PO-Revision-Date: 2011-02-25 19:13+0100\n"
 "Last-Translator: FULL NAME \n"
 "Language-Team: en \n"
@@ -17,20 +17,36 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.6\n"
 
-#: rhodecode/controllers/changeset.py:108
-#: rhodecode/controllers/changeset.py:149
-#: rhodecode/controllers/changeset.py:216
-#: rhodecode/controllers/changeset.py:229
+#: rhodecode/controllers/changelog.py:94
+msgid "All Branches"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:83
+msgid "show white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+msgid "ignore white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:157
+#, python-format
+msgid "%s line context"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:333
+#: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
 msgid "binary file"
 msgstr ""
 
-#: rhodecode/controllers/changeset.py:123
-#: rhodecode/controllers/changeset.py:168
-msgid "Changeset is to big and was cut off, see raw changeset instead"
-msgstr ""
-
-#: rhodecode/controllers/changeset.py:159
-msgid "Diff is to big and was cut off, see raw diff instead"
+#: rhodecode/controllers/changeset.py:408
+msgid ""
+"Changing status on a changeset associated witha closed pull request is "
+"not allowed"
+msgstr ""
+
+#: rhodecode/controllers/compare.py:69
+msgid "There are no changesets yet"
 msgstr ""
 
 #: rhodecode/controllers/error.py:69
@@ -59,117 +75,107 @@ msgid ""
 "fulfilling the request."
 msgstr ""
 
-#: rhodecode/controllers/feed.py:48
+#: rhodecode/controllers/feed.py:49
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
 
-#: rhodecode/controllers/feed.py:49
+#: rhodecode/controllers/feed.py:50
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: rhodecode/controllers/files.py:72
-msgid "There are no files yet"
-msgstr ""
-
-#: rhodecode/controllers/files.py:262
+#: rhodecode/controllers/feed.py:75
+msgid "commited on"
+msgstr ""
+
+#: rhodecode/controllers/files.py:84
+msgid "click here to add new file"
+msgstr ""
+
+#: rhodecode/controllers/files.py:85
+#, python-format
+msgid "There are no files yet %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
+#, python-format
+msgid "This repository is has been locked by %s on %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:266
 #, python-format
 msgid "Edited %s via RhodeCode"
 msgstr ""
 
-#: rhodecode/controllers/files.py:267
-#: rhodecode/templates/files/file_diff.html:40
+#: rhodecode/controllers/files.py:271
 msgid "No changes"
 msgstr ""
 
-#: rhodecode/controllers/files.py:278
+#: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: rhodecode/controllers/files.py:283
+#: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
 msgid "Error occurred during commit"
 msgstr ""
 
-#: rhodecode/controllers/files.py:308
+#: rhodecode/controllers/files.py:318
+#, python-format
+msgid "Added %s via RhodeCode"
+msgstr ""
+
+#: rhodecode/controllers/files.py:332
+msgid "No content"
+msgstr ""
+
+#: rhodecode/controllers/files.py:336
+msgid "No filename"
+msgstr ""
+
+#: rhodecode/controllers/files.py:378
 msgid "downloads disabled"
 msgstr ""
 
-#: rhodecode/controllers/files.py:313
+#: rhodecode/controllers/files.py:389
 #, python-format
 msgid "Unknown revision %s"
 msgstr ""
 
-#: rhodecode/controllers/files.py:315
+#: rhodecode/controllers/files.py:391
 msgid "Empty repository"
 msgstr ""
 
-#: rhodecode/controllers/files.py:317
+#: rhodecode/controllers/files.py:393
 msgid "Unknown archive type"
 msgstr ""
 
-#: rhodecode/controllers/files.py:385 rhodecode/controllers/files.py:398
-msgid "Binary file"
-msgstr ""
-
-#: rhodecode/controllers/files.py:417
-#: rhodecode/templates/changeset/changeset_range.html:4
-#: rhodecode/templates/changeset/changeset_range.html:12
-#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/controllers/files.py:494
+#: rhodecode/templates/changeset/changeset_range.html:13
+#: rhodecode/templates/changeset/changeset_range.html:31
 msgid "Changesets"
 msgstr ""
 
-#: rhodecode/controllers/files.py:418 rhodecode/controllers/summary.py:175
-#: rhodecode/templates/branches/branches.html:5
-#: rhodecode/templates/summary/summary.html:690
+#: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
+#: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
 msgid "Branches"
 msgstr ""
 
-#: rhodecode/controllers/files.py:419 rhodecode/controllers/summary.py:176
-#: rhodecode/templates/summary/summary.html:679
-#: rhodecode/templates/tags/tags.html:5
+#: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
+#: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
 msgid "Tags"
 msgstr ""
 
-#: rhodecode/controllers/journal.py:50
+#: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
 #, python-format
-msgid "%s public journal %s feed"
-msgstr ""
-
-#: rhodecode/controllers/journal.py:178 rhodecode/controllers/journal.py:212
-#: rhodecode/templates/admin/repos/repo_edit.html:171
-#: rhodecode/templates/base/base.html:50
-msgid "Public journal"
-msgstr ""
-
-#: rhodecode/controllers/login.py:111
-msgid "You have successfully registered into rhodecode"
-msgstr ""
-
-#: rhodecode/controllers/login.py:133
-msgid "Your password reset link was sent"
-msgstr ""
-
-#: rhodecode/controllers/login.py:155
 msgid ""
-"Your password reset was successful, new password has been sent to your "
-"email"
-msgstr ""
-
-#: rhodecode/controllers/search.py:109
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: rhodecode/controllers/search.py:114
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: rhodecode/controllers/search.py:118
-msgid "An error occurred during this search operation"
-msgstr ""
-
-#: rhodecode/controllers/settings.py:61 rhodecode/controllers/settings.py:171
+"%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 ""
+
+#: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was created or renamed from "
@@ -177,20 +183,89 @@ msgid ""
 "repositories"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:109
-#: rhodecode/controllers/admin/repos.py:239
+#: rhodecode/controllers/forks.py:167
+#, python-format
+msgid "forked %s repository as %s"
+msgstr ""
+
+#: rhodecode/controllers/forks.py:181
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr ""
+
+#: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
+msgid "public journal"
+msgstr ""
+
+#: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
+#: rhodecode/templates/base/base.html:220
+msgid "journal"
+msgstr ""
+
+#: rhodecode/controllers/login.py:143
+msgid "You have successfully registered into rhodecode"
+msgstr ""
+
+#: rhodecode/controllers/login.py:164
+msgid "Your password reset link was sent"
+msgstr ""
+
+#: rhodecode/controllers/login.py:184
+msgid ""
+"Your password reset was successful, new password has been sent to your "
+"email"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
+msgid "Bookmarks"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:158
+msgid "Pull request requires a title with min. 3 chars"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:160
+msgid "error during creation of pull request"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:181
+msgid "Successfully opened new pull request"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:184
+msgid "Error occurred during sending pull request"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:217
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: rhodecode/controllers/search.py:131
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
+#: rhodecode/controllers/search.py:136
+msgid "There is no index to search in. Please run whoosh indexer"
+msgstr ""
+
+#: rhodecode/controllers/search.py:140
+msgid "An error occurred during this search operation"
+msgstr ""
+
+#: rhodecode/controllers/settings.py:107
+#: rhodecode/controllers/admin/repos.py:266
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:126
-#: rhodecode/controllers/admin/repos.py:257
+#: rhodecode/controllers/settings.py:125
+#: rhodecode/controllers/admin/repos.py:284
 #, python-format
 msgid "error occurred during update of repository %s"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:144
-#: rhodecode/controllers/admin/repos.py:275
+#: rhodecode/controllers/settings.py:143
+#: rhodecode/controllers/admin/repos.py:302
 #, python-format
 msgid ""
 "%s repository is not mapped to db perhaps it was moved or renamed  from "
@@ -198,111 +273,102 @@ msgid ""
 "repositories"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:156
-#: rhodecode/controllers/admin/repos.py:287
+#: rhodecode/controllers/settings.py:155
+#: rhodecode/controllers/admin/repos.py:314
 #, python-format
 msgid "deleted repository %s"
 msgstr ""
 
 #: rhodecode/controllers/settings.py:159
-#: rhodecode/controllers/admin/repos.py:297
-#: rhodecode/controllers/admin/repos.py:303
+#: rhodecode/controllers/admin/repos.py:324
+#: rhodecode/controllers/admin/repos.py:330
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:193
-#, python-format
-msgid "forked %s repository as %s"
-msgstr ""
-
-#: rhodecode/controllers/settings.py:211
-#, python-format
-msgid "An error occurred during repository forking %s"
-msgstr ""
-
-#: rhodecode/controllers/summary.py:123
+#: rhodecode/controllers/summary.py:138
 msgid "No data loaded yet"
 msgstr ""
 
-#: rhodecode/controllers/summary.py:126
+#: rhodecode/controllers/summary.py:142
+#: rhodecode/templates/summary/summary.html:148
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:49
-msgid "BASE"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:50
-msgid "ONELEVEL"
+msgid "BASE"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:51
+msgid "ONELEVEL"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:52
 msgid "SUBTREE"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:55
-msgid "NEVER"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:56
-msgid "ALLOW"
+msgid "NEVER"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:57
-msgid "TRY"
+msgid "ALLOW"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:58
-msgid "DEMAND"
+msgid "TRY"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:59
+msgid "DEMAND"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:60
 msgid "HARD"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:63
-msgid "No encryption"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:64
-msgid "LDAPS connection"
+msgid "No encryption"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:65
+msgid "LDAPS connection"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:66
 msgid "START_TLS on LDAP connection"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:115
+#: rhodecode/controllers/admin/ldap_settings.py:126
 msgid "Ldap settings updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:120
+#: rhodecode/controllers/admin/ldap_settings.py:130
 msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:134
+#: rhodecode/controllers/admin/ldap_settings.py:147
 msgid "error occurred during update of ldap settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:56
-msgid "None"
-msgstr ""
-
-#: rhodecode/controllers/admin/permissions.py:57
-msgid "Read"
-msgstr ""
-
-#: rhodecode/controllers/admin/permissions.py:58
-msgid "Write"
-msgstr ""
-
 #: rhodecode/controllers/admin/permissions.py:59
+msgid "None"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:60
+msgid "Read"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:61
+msgid "Write"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:62
 #: rhodecode/templates/admin/ldap/ldap.html:9
 #: rhodecode/templates/admin/permissions/permissions.html:9
 #: rhodecode/templates/admin/repos/repo_add.html:9
 #: rhodecode/templates/admin/repos/repo_edit.html:9
-#: rhodecode/templates/admin/repos/repos.html:10
+#: rhodecode/templates/admin/repos/repos.html:9
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
@@ -310,550 +376,871 @@ msgstr ""
 #: rhodecode/templates/admin/settings/settings.html:9
 #: rhodecode/templates/admin/users/user_add.html:8
 #: rhodecode/templates/admin/users/user_edit.html:9
-#: rhodecode/templates/admin/users/user_edit.html:110
+#: rhodecode/templates/admin/users/user_edit.html:122
 #: rhodecode/templates/admin/users/users.html:9
 #: rhodecode/templates/admin/users_groups/users_group_add.html:8
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:9
 #: rhodecode/templates/admin/users_groups/users_groups.html:9
-#: rhodecode/templates/base/base.html:279
-#: rhodecode/templates/base/base.html:366
-#: rhodecode/templates/base/base.html:368
-#: rhodecode/templates/base/base.html:370
+#: rhodecode/templates/base/base.html:197
+#: rhodecode/templates/base/base.html:337
+#: rhodecode/templates/base/base.html:339
+#: rhodecode/templates/base/base.html:341
 msgid "Admin"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:62
+#: rhodecode/controllers/admin/permissions.py:65
 msgid "disabled"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:64
+#: rhodecode/controllers/admin/permissions.py:67
 msgid "allowed with manual account activation"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:66
-msgid "allowed with automatic account activation"
-msgstr ""
-
-#: rhodecode/controllers/admin/permissions.py:68
-msgid "Disabled"
-msgstr ""
-
 #: rhodecode/controllers/admin/permissions.py:69
+msgid "allowed with automatic account activation"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:71
+#: rhodecode/controllers/admin/permissions.py:74
+msgid "Disabled"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:72
+#: rhodecode/controllers/admin/permissions.py:75
 msgid "Enabled"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:102
+#: rhodecode/controllers/admin/permissions.py:116
 msgid "Default permissions updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:119
+#: rhodecode/controllers/admin/permissions.py:130
 msgid "error occurred during update of permissions"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:96
-#, 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 ""
-
-#: rhodecode/controllers/admin/repos.py:172
+#: rhodecode/controllers/admin/repos.py:123
+msgid "--REMOVE FORK--"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:192
 #, python-format
 msgid "created repository %s from %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:176
+#: rhodecode/controllers/admin/repos.py:196
 #, python-format
 msgid "created repository %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:205
+#: rhodecode/controllers/admin/repos.py:227
 #, python-format
 msgid "error occurred during creation of repository %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:292
+#: rhodecode/controllers/admin/repos.py:319
 #, python-format
 msgid "Cannot delete %s it still contains attached forks"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:320
+#: rhodecode/controllers/admin/repos.py:348
 msgid "An error occurred during deletion of repository user"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:335
-msgid "An error occurred during deletion of repository users groups"
-msgstr ""
-
-#: rhodecode/controllers/admin/repos.py:352
-msgid "An error occurred during deletion of repository stats"
-msgstr ""
-
 #: rhodecode/controllers/admin/repos.py:367
+msgid "An error occurred during deletion of repository users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:385
+msgid "An error occurred during deletion of repository stats"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:402
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:387
+#: rhodecode/controllers/admin/repos.py:422
+msgid "An error occurred during unlocking"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:442
 msgid "Updated repository visibility in public journal"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:390
+#: rhodecode/controllers/admin/repos.py:446
 msgid "An error occurred during setting this repository in public journal"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:395 rhodecode/model/forms.py:53
+#: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
 msgid "Token mismatch"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:408
+#: rhodecode/controllers/admin/repos.py:464
 msgid "Pulled from remote location"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:410
+#: rhodecode/controllers/admin/repos.py:466
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:83
+#: rhodecode/controllers/admin/repos.py:482
+msgid "Nothing"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:484
+#, python-format
+msgid "Marked repo %s as fork of %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:488
+msgid "An error occurred during this operation"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:116
 #, python-format
 msgid "created repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:96
+#: rhodecode/controllers/admin/repos_groups.py:129
 #, python-format
 msgid "error occurred during creation of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:130
+#: rhodecode/controllers/admin/repos_groups.py:163
 #, python-format
 msgid "updated repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:143
+#: rhodecode/controllers/admin/repos_groups.py:176
 #, python-format
 msgid "error occurred during update of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:164
+#: rhodecode/controllers/admin/repos_groups.py:194
 #, python-format
 msgid "This group contains %s repositores and cannot be deleted"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:171
+#: rhodecode/controllers/admin/repos_groups.py:202
 #, python-format
 msgid "removed repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:175
+#: rhodecode/controllers/admin/repos_groups.py:208
+msgid "Cannot delete this group it still contains subgroups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:213
+#: rhodecode/controllers/admin/repos_groups.py:218
 #, python-format
 msgid "error occurred during deletion of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:109
+#: rhodecode/controllers/admin/repos_groups.py:238
+msgid "An error occurred during deletion of group user"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:258
+msgid "An error occurred during deletion of group users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:121
 #, python-format
 msgid "Repositories successfully rescanned added: %s,removed: %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:118
+#: rhodecode/controllers/admin/settings.py:129
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:143
+#: rhodecode/controllers/admin/settings.py:160
 msgid "Updated application settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:148
-#: rhodecode/controllers/admin/settings.py:215
+#: rhodecode/controllers/admin/settings.py:164
+#: rhodecode/controllers/admin/settings.py:275
 msgid "error occurred during updating application settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:210
-msgid "Updated mercurial settings"
-msgstr ""
-
-#: rhodecode/controllers/admin/settings.py:236
+#: rhodecode/controllers/admin/settings.py:200
+msgid "Updated visualisation settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:205
+msgid "error occurred during updating visualisation settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:271
+msgid "Updated VCS settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:285
 msgid "Added new hook"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:247
+#: rhodecode/controllers/admin/settings.py:297
 msgid "Updated hooks"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:251
+#: rhodecode/controllers/admin/settings.py:301
 msgid "error occurred during hook creation"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:310
+#: rhodecode/controllers/admin/settings.py:320
+msgid "Email task created"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:375
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:339
+#: rhodecode/controllers/admin/settings.py:406
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:359
+#: rhodecode/controllers/admin/settings.py:421
+#: rhodecode/controllers/admin/users.py:191
+#, python-format
+msgid "error occurred during update of user %s"
+msgstr ""
+
 #: rhodecode/controllers/admin/users.py:130
 #, python-format
-msgid "error occurred during update of user %s"
-msgstr ""
-
-#: rhodecode/controllers/admin/users.py:78
-#, python-format
 msgid "created user %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:90
+#: rhodecode/controllers/admin/users.py:142
 #, python-format
 msgid "error occurred during creation of user %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:116
+#: rhodecode/controllers/admin/users.py:171
 msgid "User updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:146
+#: rhodecode/controllers/admin/users.py:207
 msgid "successfully deleted user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:150
+#: rhodecode/controllers/admin/users.py:212
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:166
+#: rhodecode/controllers/admin/users.py:226
 msgid "You can't edit this user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:195
-#: rhodecode/controllers/admin/users_groups.py:202
+#: rhodecode/controllers/admin/users.py:266
 msgid "Granted 'repository create' permission to user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:204
-#: rhodecode/controllers/admin/users_groups.py:211
+#: rhodecode/controllers/admin/users.py:271
 msgid "Revoked 'repository create' permission to user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:74
+#: rhodecode/controllers/admin/users.py:277
+msgid "Granted 'repository fork' permission to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:282
+msgid "Revoked 'repository fork' permission to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:288
+#: rhodecode/controllers/admin/users_groups.py:255
+msgid "An error occurred during permissions saving"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:303
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:309
+msgid "An error occurred during email saving"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:319
+msgid "Removed email from user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:84
 #, python-format
 msgid "created users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:86
+#: rhodecode/controllers/admin/users_groups.py:95
 #, python-format
 msgid "error occurred during creation of users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:119
+#: rhodecode/controllers/admin/users_groups.py:135
 #, python-format
 msgid "updated users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:138
+#: rhodecode/controllers/admin/users_groups.py:157
 #, python-format
 msgid "error occurred during update of users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:154
+#: rhodecode/controllers/admin/users_groups.py:174
 msgid "successfully deleted users group"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:158
+#: rhodecode/controllers/admin/users_groups.py:179
 msgid "An error occurred during deletion of users group"
 msgstr ""
 
-#: rhodecode/lib/__init__.py:279
-msgid "year"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:280
-msgid "month"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:281
-msgid "day"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:282
-msgid "hour"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:283
-msgid "minute"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:284
-msgid "second"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:293
-msgid "ago"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:296
-msgid "just now"
-msgstr ""
-
-#: rhodecode/lib/auth.py:377
+#: rhodecode/controllers/admin/users_groups.py:233
+msgid "Granted 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:238
+msgid "Revoked 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:244
+msgid "Granted 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:249
+msgid "Revoked 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/lib/auth.py:499
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: rhodecode/lib/auth.py:421
+#: rhodecode/lib/auth.py:540
 msgid "You need to be a signed in to view this page"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:307
+#: rhodecode/lib/diffs.py:86
+msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: rhodecode/lib/diffs.py:96
+msgid "No changes detected"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:372
+#, python-format
+msgid "%a, %d %b %Y %H:%M:%S"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:484
 msgid "True"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:311
+#: rhodecode/lib/helpers.py:488
 msgid "False"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:352
+#: rhodecode/lib/helpers.py:532
+msgid "Changeset not found"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:555
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:356
+#: rhodecode/lib/helpers.py:561
 msgid "compare view"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:365
-msgid "and"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:365
-#, python-format
-msgid "%s more"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:367 rhodecode/templates/changelog/changelog.html:14
-#: rhodecode/templates/changelog/changelog.html:39
-msgid "revisions"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:385
-msgid "fork name "
-msgstr ""
-
-#: rhodecode/lib/helpers.py:388
-msgid "[deleted] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:389 rhodecode/lib/helpers.py:393
-msgid "[created] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:390 rhodecode/lib/helpers.py:394
-msgid "[forked] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:391 rhodecode/lib/helpers.py:395
-msgid "[updated] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:392
-msgid "[delete] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:396
-msgid "[pushed] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:397
-msgid "[committed via RhodeCode] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:398
-msgid "[pulled from remote] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:399
-msgid "[pulled] from"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:400
-msgid "[started following] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:401
-msgid "[stopped following] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:577
-#, python-format
-msgid " and %s more"
-msgstr ""
-
 #: rhodecode/lib/helpers.py:581
+msgid "and"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:582
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
+msgid "revisions"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:606
+msgid "fork name "
+msgstr ""
+
+#: rhodecode/lib/helpers.py:620
+#: rhodecode/templates/pullrequests/pullrequest_show.html:4
+#: rhodecode/templates/pullrequests/pullrequest_show.html:12
+#, python-format
+msgid "Pull request #%s"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:626
+msgid "[deleted] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
+msgid "[created] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:630
+msgid "[created] repository as fork"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
+msgid "[forked] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
+msgid "[updated] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:636
+msgid "[delete] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:644
+msgid "[created] user"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:646
+msgid "[updated] user"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:648
+msgid "[created] users group"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:650
+msgid "[updated] users group"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:652
+msgid "[commented] on revision in repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:654
+msgid "[commented] on pull request for"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:656
+msgid "[closed] pull request for"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:658
+msgid "[pushed] into"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:660
+msgid "[committed via RhodeCode] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:662
+msgid "[pulled from remote] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:664
+msgid "[pulled] from"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:666
+msgid "[started following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:668
+msgid "[stopped following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:840
+#, python-format
+msgid " and %s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:844
 msgid "No Files"
 msgstr ""
 
-#: rhodecode/model/forms.py:66
-msgid "Invalid username"
-msgstr ""
-
-#: rhodecode/model/forms.py:75
-msgid "This username already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:79
+#: rhodecode/lib/utils2.py:335
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:336
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:337
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:338
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:339
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:340
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:355
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#: rhodecode/lib/utils2.py:357
+#, python-format
+msgid "%s and %s ago"
+msgstr ""
+
+#: rhodecode/lib/utils2.py:360
+msgid "just now"
+msgstr ""
+
+#: rhodecode/lib/celerylib/tasks.py:269
+msgid "password reset link"
+msgstr ""
+
+#: rhodecode/model/comment.py:110
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: rhodecode/model/comment.py:157
+msgid "[Mention]"
+msgstr ""
+
+#: rhodecode/model/db.py:1140
+msgid "Repository no access"
+msgstr ""
+
+#: rhodecode/model/db.py:1141
+msgid "Repository read access"
+msgstr ""
+
+#: rhodecode/model/db.py:1142
+msgid "Repository write access"
+msgstr ""
+
+#: rhodecode/model/db.py:1143
+msgid "Repository admin access"
+msgstr ""
+
+#: rhodecode/model/db.py:1145
+msgid "Repositories Group no access"
+msgstr ""
+
+#: rhodecode/model/db.py:1146
+msgid "Repositories Group read access"
+msgstr ""
+
+#: rhodecode/model/db.py:1147
+msgid "Repositories Group write access"
+msgstr ""
+
+#: rhodecode/model/db.py:1148
+msgid "Repositories Group admin access"
+msgstr ""
+
+#: rhodecode/model/db.py:1150
+msgid "RhodeCode Administrator"
+msgstr ""
+
+#: rhodecode/model/db.py:1151
+msgid "Repository creation disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1152
+msgid "Repository creation enabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1153
+msgid "Repository forking disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1154
+msgid "Repository forking enabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1155
+msgid "Register disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1156
+msgid "Register new user with RhodeCode with manual activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1159
+msgid "Register new user with RhodeCode with auto activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1579
+msgid "Not Reviewed"
+msgstr ""
+
+#: rhodecode/model/db.py:1580
+msgid "Approved"
+msgstr ""
+
+#: rhodecode/model/db.py:1581
+msgid "Rejected"
+msgstr ""
+
+#: rhodecode/model/db.py:1582
+msgid "Under Review"
+msgstr ""
+
+#: rhodecode/model/forms.py:43
+msgid "Please enter a login"
+msgstr ""
+
+#: rhodecode/model/forms.py:44
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr ""
+
+#: rhodecode/model/forms.py:52
+msgid "Please enter a password"
+msgstr ""
+
+#: rhodecode/model/forms.py:53
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr ""
+
+#: rhodecode/model/notification.py:220
+msgid "commented on commit"
+msgstr ""
+
+#: rhodecode/model/notification.py:221
+msgid "sent message"
+msgstr ""
+
+#: rhodecode/model/notification.py:222
+msgid "mentioned you"
+msgstr ""
+
+#: rhodecode/model/notification.py:223
+msgid "registered in RhodeCode"
+msgstr ""
+
+#: rhodecode/model/notification.py:224
+msgid "opened new pull request"
+msgstr ""
+
+#: rhodecode/model/notification.py:225
+msgid "commented on pull request"
+msgstr ""
+
+#: rhodecode/model/pull_request.py:84
+#, python-format
+msgid "%(user)s wants you to review pull request #%(pr_id)s"
+msgstr ""
+
+#: rhodecode/model/scm.py:535
+msgid "latest tip"
+msgstr ""
+
+#: rhodecode/model/user.py:230
+msgid "new user registration"
+msgstr ""
+
+#: rhodecode/model/user.py:255 rhodecode/model/user.py:277
+#: rhodecode/model/user.py:299
+msgid "You can't Edit this user since it's crucial for entire application"
+msgstr ""
+
+#: rhodecode/model/user.py:323
+msgid "You can't remove this user since it's crucial for entire application"
+msgstr ""
+
+#: rhodecode/model/user.py:329
+#, python-format
+msgid ""
+"user \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories. %s"
+msgstr ""
+
+#: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: rhodecode/model/validators.py:82
+#, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:84
+#, python-format
+msgid "Username \"%(username)s\" is forbidden"
+msgstr ""
+
+#: rhodecode/model/validators.py:86
 msgid ""
 "Username may only contain alphanumeric characters underscores, periods or"
 " dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: rhodecode/model/forms.py:94
-msgid "Invalid group name"
-msgstr ""
-
-#: rhodecode/model/forms.py:104
-msgid "This users group already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:110
+#: rhodecode/model/validators.py:114
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
+#: rhodecode/model/validators.py:133
+msgid "Invalid users group name"
+msgstr ""
+
+#: rhodecode/model/validators.py:134
+#, python-format
+msgid "Users group \"%(usersgroup)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:136
 msgid ""
-"Group name may only contain alphanumeric characters underscores, periods "
-"or dashes and must begin with alphanumeric character"
-msgstr ""
-
-#: rhodecode/model/forms.py:132
+"users group name may only contain  alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: rhodecode/model/validators.py:174
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: rhodecode/model/forms.py:148
-msgid "This group already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:164 rhodecode/model/forms.py:172
-#: rhodecode/model/forms.py:180
-msgid "Invalid characters in password"
-msgstr ""
-
-#: rhodecode/model/forms.py:191
+#: rhodecode/model/validators.py:175
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:177
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:235
+msgid "Invalid characters (non-ascii) in password"
+msgstr ""
+
+#: rhodecode/model/validators.py:250
 msgid "Passwords do not match"
 msgstr ""
 
-#: rhodecode/model/forms.py:196
+#: rhodecode/model/validators.py:267
 msgid "invalid password"
 msgstr ""
 
-#: rhodecode/model/forms.py:197
+#: rhodecode/model/validators.py:268
 msgid "invalid user name"
 msgstr ""
 
-#: rhodecode/model/forms.py:198
+#: rhodecode/model/validators.py:269
 msgid "Your account is disabled"
 msgstr ""
 
-#: rhodecode/model/forms.py:233
-msgid "This username is not valid"
-msgstr ""
-
-#: rhodecode/model/forms.py:245
-msgid "This repository name is disallowed"
-msgstr ""
-
-#: rhodecode/model/forms.py:266
+#: rhodecode/model/validators.py:313
+#, python-format
+msgid "Repository name %(repo)s is disallowed"
+msgstr ""
+
+#: rhodecode/model/validators.py:315
 #, python-format
-msgid "This repository already exists in group \"%s\""
-msgstr ""
-
-#: rhodecode/model/forms.py:274
-msgid "This repository already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:312 rhodecode/model/forms.py:319
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:316
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr ""
+
+#: rhodecode/model/validators.py:318
+#, python-format
+msgid "Repositories group with name \"%(repo)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:431
 msgid "invalid clone url"
 msgstr ""
 
-#: rhodecode/model/forms.py:322
-msgid "Invalid clone url, provide a valid clone http\\s url"
-msgstr ""
-
-#: rhodecode/model/forms.py:334
-msgid "Fork have to be the same type as original"
-msgstr ""
-
-#: rhodecode/model/forms.py:341
+#: rhodecode/model/validators.py:432
+msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
+msgstr ""
+
+#: rhodecode/model/validators.py:457
+msgid "Fork have to be the same type as parent"
+msgstr ""
+
+#: rhodecode/model/validators.py:478
 msgid "This username or users group name is not valid"
 msgstr ""
 
-#: rhodecode/model/forms.py:403
+#: rhodecode/model/validators.py:562
 msgid "This is not a valid path"
 msgstr ""
 
-#: rhodecode/model/forms.py:416
+#: rhodecode/model/validators.py:577
 msgid "This e-mail address is already taken"
 msgstr ""
 
-#: rhodecode/model/forms.py:427
-msgid "This e-mail address doesn't exist."
-msgstr ""
-
-#: rhodecode/model/forms.py:447
+#: rhodecode/model/validators.py:597
+#, python-format
+msgid "e-mail \"%(email)s\" does not exist."
+msgstr ""
+
+#: rhodecode/model/validators.py:634
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name "
-"of the attribute that is equivalent to 'username'"
-msgstr ""
-
-#: rhodecode/model/forms.py:466
-msgid "Please enter a login"
-msgstr ""
-
-#: rhodecode/model/forms.py:467
-#, python-format
-msgid "Enter a value %(min)i characters long or more"
-msgstr ""
-
-#: rhodecode/model/forms.py:475
-msgid "Please enter a password"
-msgstr ""
-
-#: rhodecode/model/forms.py:476
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: rhodecode/model/validators.py:653
 #, python-format
-msgid "Enter %(min)i characters or more"
-msgstr ""
-
-#: rhodecode/model/user.py:145
-msgid "[RhodeCode] New User registration"
-msgstr ""
-
-#: rhodecode/model/user.py:157 rhodecode/model/user.py:179
-msgid "You can't Edit this user since it's crucial for entire application"
-msgstr ""
-
-#: rhodecode/model/user.py:201
-msgid "You can't remove this user since it's crucial for entire application"
-msgstr ""
-
-#: rhodecode/model/user.py:204
-#, python-format
-msgid ""
-"This user still owns %s repositories and cannot be removed. Switch owners"
-" or remove those repositories"
-msgstr ""
-
-#: rhodecode/templates/index.html:4
+msgid "Revisions %(revs)s are already part of pull request or have set status"
+msgstr ""
+
+#: rhodecode/templates/index.html:3
 msgid "Dashboard"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:22
-#: rhodecode/templates/admin/users/user_edit_my_account.html:102
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/repo_switcher_list.html:4
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/users/user_edit_my_account.html:31
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/bookmarks/bookmarks.html:10
+#: rhodecode/templates/branches/branches.html:9
+#: rhodecode/templates/journal/journal.html:40
+#: rhodecode/templates/tags/tags.html:10
 msgid "quick filter..."
 msgstr ""
 
-#: rhodecode/templates/index_base.html:23
-#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/base/base.html:221
 msgid "repositories"
 msgstr ""
 
+#: rhodecode/templates/index_base.html:13
+#: rhodecode/templates/index_base.html:15
+#: rhodecode/templates/admin/repos/repos.html:21
+msgid "ADD REPOSITORY"
+msgstr ""
+
 #: rhodecode/templates/index_base.html:29
-#: rhodecode/templates/admin/repos/repos.html:22
-msgid "ADD NEW REPOSITORY"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
@@ -862,158 +1249,158 @@ msgstr ""
 msgid "Group name"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:42
-#: rhodecode/templates/index_base.html:73
-#: rhodecode/templates/admin/repos/repo_add_base.html:44
-#: rhodecode/templates/admin/repos/repo_edit.html:64
-#: rhodecode/templates/admin/repos/repos.html:31
+#: rhodecode/templates/index_base.html:30
+#: rhodecode/templates/index_base.html:71
+#: rhodecode/templates/index_base.html:142
+#: rhodecode/templates/index_base.html:168
+#: rhodecode/templates/admin/repos/repo_add_base.html:56
+#: rhodecode/templates/admin/repos/repo_edit.html:75
+#: rhodecode/templates/admin/repos/repos.html:72
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
-#: rhodecode/templates/settings/repo_fork.html:40
-#: rhodecode/templates/settings/repo_settings.html:40
-#: rhodecode/templates/summary/summary.html:92
+#: rhodecode/templates/forks/fork.html:59
+#: rhodecode/templates/settings/repo_settings.html:66
+#: rhodecode/templates/summary/summary.html:105
 msgid "Description"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:53
+#: rhodecode/templates/index_base.html:40
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
 msgid "Repositories group"
 msgstr ""
 
+#: rhodecode/templates/index_base.html:70
+#: rhodecode/templates/index_base.html:166
+#: rhodecode/templates/admin/repos/repo_add_base.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:32
+#: rhodecode/templates/admin/repos/repos.html:70
+#: rhodecode/templates/admin/users/user_edit.html:192
+#: rhodecode/templates/admin/users/user_edit_my_account.html:59
+#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
+#: rhodecode/templates/bookmarks/bookmarks.html:36
+#: rhodecode/templates/bookmarks/bookmarks_data.html:6
+#: rhodecode/templates/branches/branches.html:51
+#: rhodecode/templates/files/files_browser.html:47
+#: rhodecode/templates/journal/journal.html:59
+#: rhodecode/templates/journal/journal.html:107
+#: rhodecode/templates/journal/journal.html:186
+#: rhodecode/templates/settings/repo_settings.html:31
+#: rhodecode/templates/summary/summary.html:43
+#: rhodecode/templates/summary/summary.html:123
+#: rhodecode/templates/tags/tags.html:36
+#: rhodecode/templates/tags/tags_data.html:6
+msgid "Name"
+msgstr ""
+
 #: rhodecode/templates/index_base.html:72
-#: rhodecode/templates/admin/repos/repo_add_base.html:9
-#: rhodecode/templates/admin/repos/repo_edit.html:32
-#: rhodecode/templates/admin/repos/repos.html:30
-#: rhodecode/templates/admin/users/user_edit_my_account.html:117
-#: rhodecode/templates/files/files_browser.html:157
-#: rhodecode/templates/settings/repo_settings.html:31
-#: rhodecode/templates/summary/summary.html:31
-#: rhodecode/templates/summary/summary.html:107
-msgid "Name"
+msgid "Last change"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:73
+#: rhodecode/templates/index_base.html:171
+#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/journal/journal.html:188
+msgid "Tip"
 msgstr ""
 
 #: rhodecode/templates/index_base.html:74
-#: rhodecode/templates/admin/repos/repos.html:32
-#: rhodecode/templates/summary/summary.html:114
-msgid "Last change"
+#: rhodecode/templates/index_base.html:173
+#: rhodecode/templates/admin/repos/repo_edit.html:121
+#: rhodecode/templates/admin/repos/repos.html:73
+msgid "Owner"
 msgstr ""
 
 #: rhodecode/templates/index_base.html:75
-#: rhodecode/templates/admin/repos/repos.html:33
-msgid "Tip"
+#: rhodecode/templates/summary/summary.html:48
+#: rhodecode/templates/summary/summary.html:51
+msgid "RSS"
 msgstr ""
 
 #: rhodecode/templates/index_base.html:76
-#: rhodecode/templates/admin/repos/repo_edit.html:97
-msgid "Owner"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:77
-#: rhodecode/templates/journal/public_journal.html:20
-#: rhodecode/templates/summary/summary.html:180
-#: rhodecode/templates/summary/summary.html:183
-msgid "RSS"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:78
-#: rhodecode/templates/journal/public_journal.html:23
-#: rhodecode/templates/summary/summary.html:181
-#: rhodecode/templates/summary/summary.html:184
 msgid "Atom"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:87
-#: rhodecode/templates/index_base.html:89
-#: rhodecode/templates/index_base.html:91
-#: rhodecode/templates/base/base.html:209
-#: rhodecode/templates/base/base.html:211
-#: rhodecode/templates/base/base.html:213
-#: rhodecode/templates/summary/summary.html:4
-msgid "Summary"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:95
-#: rhodecode/templates/index_base.html:97
-#: rhodecode/templates/index_base.html:99
-#: rhodecode/templates/base/base.html:225
-#: rhodecode/templates/base/base.html:227
-#: rhodecode/templates/base/base.html:229
-#: rhodecode/templates/changelog/changelog.html:6
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "Changelog"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:103
-#: rhodecode/templates/index_base.html:105
-#: rhodecode/templates/index_base.html:107
-#: rhodecode/templates/base/base.html:268
-#: rhodecode/templates/base/base.html:270
-#: rhodecode/templates/base/base.html:272
-#: rhodecode/templates/files/files.html:4
-msgid "Files"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:116
-#: rhodecode/templates/admin/repos/repos.html:42
-#: rhodecode/templates/admin/users/user_edit_my_account.html:127
-#: rhodecode/templates/summary/summary.html:48
-msgid "Mercurial repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:118
-#: rhodecode/templates/admin/repos/repos.html:44
-#: rhodecode/templates/admin/users/user_edit_my_account.html:129
-#: rhodecode/templates/summary/summary.html:51
-msgid "Git repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:123
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
-#: rhodecode/templates/journal/journal.html:53
-#: rhodecode/templates/summary/summary.html:56
-msgid "private repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:125
-#: rhodecode/templates/journal/journal.html:55
-#: rhodecode/templates/summary/summary.html:58
-msgid "public repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:133
-#: rhodecode/templates/base/base.html:291
-#: rhodecode/templates/settings/repo_fork.html:13
-msgid "fork"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:134
-#: rhodecode/templates/admin/repos/repos.html:60
-#: rhodecode/templates/admin/users/user_edit_my_account.html:143
-#: rhodecode/templates/summary/summary.html:69
-#: rhodecode/templates/summary/summary.html:71
-msgid "Fork of"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:155
-#: rhodecode/templates/admin/repos/repos.html:73
-msgid "No changesets yet"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:161
-#: rhodecode/templates/index_base.html:163
+#: rhodecode/templates/index_base.html:110
+#: rhodecode/templates/index_base.html:112
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:168
-#: rhodecode/templates/index_base.html:170
+#: rhodecode/templates/index_base.html:117
+#: rhodecode/templates/index_base.html:119
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
+#: rhodecode/templates/index_base.html:140
+msgid "Group Name"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:158
+#: rhodecode/templates/index_base.html:198
+#: rhodecode/templates/admin/repos/repos.html:94
+#: rhodecode/templates/admin/users/user_edit_my_account.html:179
+#: rhodecode/templates/admin/users/users.html:107
+#: rhodecode/templates/bookmarks/bookmarks.html:60
+#: rhodecode/templates/branches/branches.html:77
+#: rhodecode/templates/journal/journal.html:211
+#: rhodecode/templates/tags/tags.html:60
+msgid "Click to sort ascending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:159
+#: rhodecode/templates/index_base.html:199
+#: rhodecode/templates/admin/repos/repos.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account.html:180
+#: rhodecode/templates/admin/users/users.html:108
+#: rhodecode/templates/bookmarks/bookmarks.html:61
+#: rhodecode/templates/branches/branches.html:78
+#: rhodecode/templates/journal/journal.html:212
+#: rhodecode/templates/tags/tags.html:61
+msgid "Click to sort descending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:169
+msgid "Last Change"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:200
+#: rhodecode/templates/admin/repos/repos.html:96
+#: rhodecode/templates/admin/users/user_edit_my_account.html:181
+#: rhodecode/templates/admin/users/users.html:109
+#: rhodecode/templates/bookmarks/bookmarks.html:62
+#: rhodecode/templates/branches/branches.html:79
+#: rhodecode/templates/journal/journal.html:213
+#: rhodecode/templates/tags/tags.html:62
+msgid "No records found."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:201
+#: rhodecode/templates/admin/repos/repos.html:97
+#: rhodecode/templates/admin/users/user_edit_my_account.html:182
+#: rhodecode/templates/admin/users/users.html:110
+#: rhodecode/templates/bookmarks/bookmarks.html:63
+#: rhodecode/templates/branches/branches.html:80
+#: rhodecode/templates/journal/journal.html:214
+#: rhodecode/templates/tags/tags.html:63
+msgid "Data error."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:202
+#: rhodecode/templates/admin/repos/repos.html:98
+#: rhodecode/templates/admin/users/user_edit_my_account.html:183
+#: rhodecode/templates/admin/users/users.html:111
+#: rhodecode/templates/bookmarks/bookmarks.html:64
+#: rhodecode/templates/branches/branches.html:81
+#: rhodecode/templates/journal/journal.html:215
+#: rhodecode/templates/tags/tags.html:64
+msgid "Loading..."
+msgstr ""
+
 #: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
-#: rhodecode/templates/base/base.html:38
 msgid "Sign In"
 msgstr ""
 
@@ -1024,25 +1411,29 @@ msgstr ""
 #: 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:47
-#: rhodecode/templates/admin/users/user_edit_my_account.html:45
-#: rhodecode/templates/base/base.html:15
-#: rhodecode/templates/summary/summary.html:106
+#: rhodecode/templates/admin/users/user_edit.html:50
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
+#: rhodecode/templates/base/base.html:83
+#: rhodecode/templates/summary/summary.html:122
 msgid "Username"
 msgstr ""
 
 #: 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:24
+#: rhodecode/templates/base/base.html:92
 msgid "Password"
 msgstr ""
 
+#: rhodecode/templates/login.html:50
+msgid "Remember me"
+msgstr ""
+
 #: rhodecode/templates/login.html:60
 msgid "Forgot your password ?"
 msgstr ""
 
-#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:35
+#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
 msgid "Don't have an account ?"
 msgstr ""
 
@@ -1079,24 +1470,24 @@ msgid "Re-enter password"
 msgstr ""
 
 #: rhodecode/templates/register.html:47
-#: rhodecode/templates/admin/users/user_add.html:50
-#: rhodecode/templates/admin/users/user_edit.html:74
-#: rhodecode/templates/admin/users/user_edit_my_account.html:63
+#: rhodecode/templates/admin/users/user_add.html:59
+#: rhodecode/templates/admin/users/user_edit.html:86
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
 msgid "First Name"
 msgstr ""
 
 #: rhodecode/templates/register.html:56
-#: rhodecode/templates/admin/users/user_add.html:59
-#: rhodecode/templates/admin/users/user_edit.html:83
-#: rhodecode/templates/admin/users/user_edit_my_account.html:72
+#: rhodecode/templates/admin/users/user_add.html:68
+#: rhodecode/templates/admin/users/user_edit.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
 msgid "Last Name"
 msgstr ""
 
 #: rhodecode/templates/register.html:65
-#: rhodecode/templates/admin/users/user_add.html:68
-#: rhodecode/templates/admin/users/user_edit.html:92
-#: rhodecode/templates/admin/users/user_edit_my_account.html:81
-#: rhodecode/templates/summary/summary.html:108
+#: rhodecode/templates/admin/users/user_add.html:77
+#: rhodecode/templates/admin/users/user_edit.html:104
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
+#: rhodecode/templates/summary/summary.html:124
 msgid "Email"
 msgstr ""
 
@@ -1108,20 +1499,59 @@ msgstr ""
 msgid "Your account must wait for activation by administrator"
 msgstr ""
 
-#: rhodecode/templates/repo_switcher_list.html:14
+#: rhodecode/templates/repo_switcher_list.html:11
+#: rhodecode/templates/admin/repos/repo_add_base.html:65
+#: rhodecode/templates/admin/repos/repo_edit.html:85
+#: rhodecode/templates/settings/repo_settings.html:76
 msgid "Private repository"
 msgstr ""
 
-#: rhodecode/templates/repo_switcher_list.html:19
+#: rhodecode/templates/repo_switcher_list.html:16
 msgid "Public repository"
 msgstr ""
 
+#: rhodecode/templates/switch_to_list.html:3
+#: rhodecode/templates/branches/branches.html:14
+msgid "branches"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:10
+#: rhodecode/templates/branches/branches_data.html:57
+msgid "There are no branches yet"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:15
+#: rhodecode/templates/shortlog/shortlog_data.html:10
+#: rhodecode/templates/tags/tags.html:15
+msgid "tags"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:22
+#: rhodecode/templates/tags/tags_data.html:33
+msgid "There are no tags yet"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:28
+#: rhodecode/templates/bookmarks/bookmarks.html:15
+msgid "bookmarks"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:35
+#: rhodecode/templates/bookmarks/bookmarks_data.html:32
+msgid "There are no bookmarks yet"
+msgstr ""
+
 #: rhodecode/templates/admin/admin.html:5
 #: rhodecode/templates/admin/admin.html:9
 msgid "Admin journal"
 msgstr ""
 
 #: rhodecode/templates/admin/admin_log.html:6
+#: rhodecode/templates/admin/repos/repos.html:74
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
+#: rhodecode/templates/journal/journal.html:61
+#: rhodecode/templates/journal/journal.html:62
 msgid "Action"
 msgstr ""
 
@@ -1130,6 +1560,11 @@ msgid "Repository"
 msgstr ""
 
 #: rhodecode/templates/admin/admin_log.html:8
+#: rhodecode/templates/bookmarks/bookmarks.html:37
+#: rhodecode/templates/bookmarks/bookmarks_data.html:7
+#: rhodecode/templates/branches/branches.html:52
+#: rhodecode/templates/tags/tags.html:37
+#: rhodecode/templates/tags/tags_data.html:7
 msgid "Date"
 msgstr ""
 
@@ -1137,7 +1572,7 @@ msgstr ""
 msgid "From IP"
 msgstr ""
 
-#: rhodecode/templates/admin/admin_log.html:52
+#: rhodecode/templates/admin/admin_log.html:53
 msgid "No actions yet"
 msgstr ""
 
@@ -1214,23 +1649,64 @@ msgid "E-mail Attribute"
 msgstr ""
 
 #: rhodecode/templates/admin/ldap/ldap.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:141
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
 #: rhodecode/templates/admin/settings/hooks.html:73
-#: rhodecode/templates/admin/users/user_edit.html:117
-#: rhodecode/templates/admin/users/user_edit.html:142
-#: rhodecode/templates/admin/users/user_edit_my_account.html:89
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:263
+#: rhodecode/templates/admin/users/user_edit.html:129
+#: rhodecode/templates/admin/users/user_edit.html:174
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:135
+#: rhodecode/templates/settings/repo_settings.html:93
 msgid "Save"
 msgstr ""
 
+#: rhodecode/templates/admin/notifications/notifications.html:5
+#: rhodecode/templates/admin/notifications/notifications.html:9
+msgid "My Notifications"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:29
+msgid "All"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:30
+#, fuzzy
+msgid "Comments"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:31
+#: rhodecode/templates/base/base.html:254
+#: rhodecode/templates/base/base.html:256
+msgid "Pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:35
+msgid "Mark all read"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications_data.html:39
+msgid "No notifications here yet"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/show_notification.html:5
+#: rhodecode/templates/admin/notifications/show_notification.html:11
+msgid "Show notification"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/show_notification.html:9
+msgid "Notifications"
+msgstr ""
+
 #: rhodecode/templates/admin/permissions/permissions.html:5
 msgid "Permissions administration"
 msgstr ""
 
 #: rhodecode/templates/admin/permissions/permissions.html:11
-#: rhodecode/templates/admin/repos/repo_edit.html:109
-#: rhodecode/templates/admin/users/user_edit.html:127
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:248
-#: rhodecode/templates/settings/repo_settings.html:58
+#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
+#: rhodecode/templates/admin/users/user_edit.html:139
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:100
+#: rhodecode/templates/settings/repo_settings.html:86
 msgid "Permissions"
 msgstr ""
 
@@ -1266,6 +1742,11 @@ msgid "Repository creation"
 msgstr ""
 
 #: rhodecode/templates/admin/permissions/permissions.html:71
+msgid "Repository forking"
+msgstr ""
+
+#: rhodecode/templates/admin/permissions/permissions.html:78
+#: rhodecode/templates/admin/repos/repo_edit.html:241
 msgid "set"
 msgstr ""
 
@@ -1276,7 +1757,6 @@ msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_add.html:11
 #: rhodecode/templates/admin/repos/repo_edit.html:11
-#: rhodecode/templates/admin/repos/repos.html:10
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
 msgid "Repositories"
 msgstr ""
@@ -1286,30 +1766,70 @@ msgid "add new"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_add_base.html:20
-#: rhodecode/templates/summary/summary.html:80
-#: rhodecode/templates/summary/summary.html:82
+#: rhodecode/templates/summary/summary.html:95
+#: rhodecode/templates/summary/summary.html:96
 msgid "Clone from"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:28
-#: rhodecode/templates/admin/repos/repo_edit.html:48
+#: rhodecode/templates/admin/repos/repo_add_base.html:24
+#: rhodecode/templates/admin/repos/repo_edit.html:44
+#: rhodecode/templates/settings/repo_settings.html:43
+msgid "Optional http[s] url from which repository should be cloned."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:29
+#: rhodecode/templates/admin/repos/repo_edit.html:49
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:4
+#: rhodecode/templates/forks/fork.html:50
+#: rhodecode/templates/settings/repo_settings.html:48
 msgid "Repository group"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:36
-#: rhodecode/templates/admin/repos/repo_edit.html:56
+#: rhodecode/templates/admin/repos/repo_add_base.html:33
+#: rhodecode/templates/forks/fork.html:54
+msgid "Optionaly select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:38
+#: rhodecode/templates/admin/repos/repo_edit.html:58
 msgid "Type"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:52
-#: rhodecode/templates/admin/repos/repo_edit.html:73
-#: rhodecode/templates/settings/repo_fork.html:48
-#: rhodecode/templates/settings/repo_settings.html:49
-msgid "Private"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_add_base.html:59
+#: rhodecode/templates/admin/repos/repo_add_base.html:42
+msgid "Type of repository to create."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:47
+#: rhodecode/templates/admin/repos/repo_edit.html:66
+#: rhodecode/templates/forks/fork.html:41
+#: rhodecode/templates/settings/repo_settings.html:57
+msgid "Landing revision"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:51
+#: rhodecode/templates/admin/repos/repo_edit.html:70
+#: rhodecode/templates/forks/fork.html:45
+#: rhodecode/templates/settings/repo_settings.html:61
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:60
+#: 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."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:69
+#: 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 ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:73
 msgid "add"
 msgstr ""
 
@@ -1323,183 +1843,269 @@ msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit.html:13
 #: rhodecode/templates/admin/users/user_edit.html:13
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
+#: rhodecode/templates/admin/users/user_edit.html:224
+#: rhodecode/templates/admin/users/user_edit.html:226
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:13
-#: rhodecode/templates/files/files_annotate.html:49
-#: rhodecode/templates/files/files_source.html:20
+#: rhodecode/templates/files/files_source.html:44
+#: rhodecode/templates/journal/journal.html:81
 msgid "edit"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit.html:40
+#: rhodecode/templates/settings/repo_settings.html:39
 msgid "Clone uri"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:81
+#: rhodecode/templates/admin/repos/repo_edit.html:53
+#: rhodecode/templates/settings/repo_settings.html:52
+msgid "Optional select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:94
 msgid "Enable statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:98
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:103
 msgid "Enable downloads"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:127
+#: rhodecode/templates/admin/repos/repo_edit.html:107
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:112
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
+msgid "Enable locking"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:116
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:126
+msgid "Change owner of this repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:142
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
+#: rhodecode/templates/admin/settings/settings.html:113
+#: rhodecode/templates/admin/settings/settings.html:168
+#: rhodecode/templates/admin/settings/settings.html:258
+#: rhodecode/templates/admin/users/user_edit.html:130
+#: rhodecode/templates/admin/users/user_edit.html:175
+#: rhodecode/templates/admin/users/user_edit.html:278
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:136
+#: rhodecode/templates/files/files_add.html:82
+#: rhodecode/templates/files/files_edit.html:68
+#: rhodecode/templates/pullrequests/pullrequest.html:124
+#: rhodecode/templates/settings/repo_settings.html:94
+msgid "Reset"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:152
 msgid "Administration"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:130
+#: rhodecode/templates/admin/repos/repo_edit.html:155
 msgid "Statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Reset current statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Confirm to remove current statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:137
-msgid "Fetched to rev"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:138
-msgid "Percentage of stats gathered"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:147
-msgid "Remote"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:151
-msgid "Pull changes from remote location"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:151
-msgid "Confirm to pull changes from remote side"
-msgstr ""
-
 #: rhodecode/templates/admin/repos/repo_edit.html:162
+msgid "Fetched to rev"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:163
+msgid "Stats gathered"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:171
+msgid "Remote"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Pull changes from remote location"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Confirm to pull changes from remote side"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:186
 msgid "Cache"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Invalidate repository cache"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Confirm to invalidate repository cache"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:177
+#: rhodecode/templates/admin/repos/repo_edit.html:195
+#: rhodecode/templates/base/base.html:318
+#: rhodecode/templates/base/base.html:320
+#: rhodecode/templates/base/base.html:322
+msgid "Public journal"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:201
 msgid "Remove from public journal"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:179
+#: rhodecode/templates/admin/repos/repo_edit.html:203
 msgid "Add to public journal"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:185
+#: rhodecode/templates/admin/repos/repo_edit.html:208
+msgid ""
+"All actions made on this repository will be accessible to everyone in "
+"public journal"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:215
+msgid "Locking"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Unlock locked repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Confirm to unlock repository"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "lock repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "Confirm to lock repository"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:224
+msgid "Repository is not locked"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:229
+msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:236
+msgid "Set as fork of"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:245
+msgid "Manually set this repository as a fork of another from the list"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:251
+#: rhodecode/templates/changeset/changeset_file_comment.html:26
 msgid "Delete"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
+#: rhodecode/templates/admin/repos/repo_edit.html:255
 msgid "Remove this repository"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
-#: rhodecode/templates/admin/repos/repos.html:79
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+#: rhodecode/templates/journal/journal.html:84
 msgid "Confirm to delete this repository"
 msgstr ""
 
+#: rhodecode/templates/admin/repos/repo_edit.html:259
+msgid ""
+"This repository will be renamed in a special way in order to be "
+"unaccesible for RhodeCode and VCS systems.\n"
+"                         If you need fully delete it from filesystem "
+"please do it manually"
+msgstr ""
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:3
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
 msgid "none"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:4
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
 msgid "read"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:5
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
 msgid "write"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:6
-#: rhodecode/templates/admin/users/users.html:38
-#: rhodecode/templates/base/base.html:296
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
+#: rhodecode/templates/admin/users/users.html:85
+#: rhodecode/templates/base/base.html:217
 msgid "admin"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:7
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
 msgid "member"
 msgstr ""
 
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
+#: rhodecode/templates/data_table/_dt_elements.html:67
+#: rhodecode/templates/journal/journal.html:132
+#: rhodecode/templates/summary/summary.html:76
+msgid "private repository"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:19
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:28
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
+msgid "default"
+msgstr ""
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:33
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:53
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:58
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
 msgid "revoke"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:75
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:83
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
 msgid "Add another member"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:89
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:97
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
 msgid "Failed to remove user"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:104
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:112
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
 msgid "Failed to remove users group"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:205
-msgid "Group"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:206
-#: rhodecode/templates/admin/users_groups/users_groups.html:33
-msgid "members"
-msgstr ""
-
 #: rhodecode/templates/admin/repos/repos.html:5
 msgid "Repositories administration"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repos.html:34
-#: rhodecode/templates/summary/summary.html:100
-msgid "Contact"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:35
-#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
-#: rhodecode/templates/admin/users/user_edit_my_account.html:119
-#: rhodecode/templates/admin/users/users.html:40
-#: rhodecode/templates/admin/users_groups/users_groups.html:35
-msgid "action"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:51
-#: rhodecode/templates/admin/users/user_edit_my_account.html:134
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
-msgid "private"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:53
-#: rhodecode/templates/admin/repos/repos.html:59
-#: rhodecode/templates/admin/users/user_edit_my_account.html:136
-#: rhodecode/templates/admin/users/user_edit_my_account.html:142
-#: rhodecode/templates/summary/summary.html:68
-msgid "public"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:79
-#: rhodecode/templates/admin/users/users.html:55
-msgid "delete"
-msgstr ""
-
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:8
 msgid "Groups"
 msgstr ""
 
-#: rhodecode/templates/admin/repos_groups/repos_groups.html:13
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:12
 msgid "with"
 msgstr ""
 
@@ -1522,10 +2128,10 @@ msgid "Group parent"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
-#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
-#: rhodecode/templates/admin/users/user_add.html:85
+#: rhodecode/templates/admin/users/user_add.html:94
 #: rhodecode/templates/admin/users_groups/users_group_add.html:49
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:90
+#: rhodecode/templates/pullrequests/pullrequest_show.html:113
 msgid "save"
 msgstr ""
 
@@ -1537,6 +2143,12 @@ msgstr ""
 msgid "edit repos group"
 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"
+msgstr ""
+
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
 msgid "Repositories groups administration"
 msgstr ""
@@ -1546,11 +2158,26 @@ msgid "ADD NEW GROUP"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
-msgid "Number of repositories"
+msgid "Number of toplevel repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
+#: rhodecode/templates/admin/users/users.html:87
+#: rhodecode/templates/admin/users_groups/users_groups.html:35
+msgid "action"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
-msgid "Confirm to delete this group"
+#: rhodecode/templates/admin/users/user_edit.html:255
+#: rhodecode/templates/admin/users_groups/users_groups.html:44
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#: rhodecode/templates/data_table/_dt_elements.html:103
+msgid "delete"
+msgstr ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#, python-format
+msgid "Confirm to delete this group: %s"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
@@ -1564,7 +2191,6 @@ msgstr ""
 
 #: rhodecode/templates/admin/settings/hooks.html:9
 #: rhodecode/templates/admin/settings/settings.html:9
-#: rhodecode/templates/settings/repo_settings.html:5
 #: rhodecode/templates/settings/repo_settings.html:13
 msgid "Settings"
 msgstr ""
@@ -1604,115 +2230,185 @@ msgstr ""
 msgid "destroy old data"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:45
+#: rhodecode/templates/admin/settings/settings.html:41
+msgid ""
+"Rescan repositories location for new repositories. Also deletes obsolete "
+"if `destroy` flag is checked "
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:46
 msgid "Rescan repositories"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:51
+#: rhodecode/templates/admin/settings/settings.html:52
 msgid "Whoosh indexing"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:59
+#: rhodecode/templates/admin/settings/settings.html:60
 msgid "index build option"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:64
+#: rhodecode/templates/admin/settings/settings.html:65
 msgid "build from scratch"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:70
+#: rhodecode/templates/admin/settings/settings.html:71
 msgid "Reindex"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:76
+#: rhodecode/templates/admin/settings/settings.html:77
 msgid "Global application settings"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:85
+#: rhodecode/templates/admin/settings/settings.html:86
 msgid "Application name"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:94
+#: rhodecode/templates/admin/settings/settings.html:95
 msgid "Realm text"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:103
+#: rhodecode/templates/admin/settings/settings.html:104
 msgid "GA code"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:111
-#: rhodecode/templates/admin/settings/settings.html:177
-msgid "Save settings"
-msgstr ""
-
 #: rhodecode/templates/admin/settings/settings.html:112
-#: rhodecode/templates/admin/settings/settings.html:178
-#: rhodecode/templates/admin/users/user_edit.html:118
-#: rhodecode/templates/admin/users/user_edit.html:143
-#: rhodecode/templates/admin/users/user_edit_my_account.html:90
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:264
-#: rhodecode/templates/files/files_edit.html:50
-msgid "Reset"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:118
-msgid "Mercurial settings"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:127
+#: rhodecode/templates/admin/settings/settings.html:167
+#: rhodecode/templates/admin/settings/settings.html:257
+msgid "Save settings"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:119
+msgid "Visualisation settings"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:128
+msgid "Icons"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:133
+msgid "Show public repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:137
+msgid "Show private repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:144
+msgid "Meta-Tagging"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:149
+msgid "Stylify recognised metatags:"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:176
+msgid "VCS settings"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:185
 msgid "Web"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:132
-msgid "require ssl for pushing"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:139
+#: rhodecode/templates/admin/settings/settings.html:190
+msgid "require ssl for vcs operations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:192
+msgid ""
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
+"will return HTTP Error 406: Not Acceptable"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:198
 msgid "Hooks"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:142
-msgid "advanced setup"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:147
+#: rhodecode/templates/admin/settings/settings.html:203
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:151
+#: rhodecode/templates/admin/settings/settings.html:207
 msgid "Show repository size after push"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:155
+#: rhodecode/templates/admin/settings/settings.html:211
 msgid "Log user push commands"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:159
+#: rhodecode/templates/admin/settings/settings.html:215
 msgid "Log user pull commands"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:166
+#: rhodecode/templates/admin/settings/settings.html:219
+msgid "advanced setup"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:224
+msgid "Mercurial Extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:229
+msgid "largefiles extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:233
+msgid "hgsubversion extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:235
+msgid ""
+"Requires hgsubversion library installed. Allows clonning from svn remote "
+"locations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:245
 msgid "Repositories location"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:171
+#: rhodecode/templates/admin/settings/settings.html:250
 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 ""
 
-#: rhodecode/templates/admin/settings/settings.html:172
+#: rhodecode/templates/admin/settings/settings.html:251
 msgid "unlock"
 msgstr ""
 
+#: rhodecode/templates/admin/settings/settings.html:252
+msgid ""
+"Location where repositories are stored. After changing this value a "
+"restart, and rescan is required"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:272
+msgid "Test Email"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:280
+msgid "Email to"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:288
+msgid "Send"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:294
+msgid "System Info and Packages"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:297
+msgid "show"
+msgstr ""
+
 #: rhodecode/templates/admin/users/user_add.html:5
 msgid "Add user"
 msgstr ""
 
 #: rhodecode/templates/admin/users/user_add.html:10
 #: rhodecode/templates/admin/users/user_edit.html:11
-#: rhodecode/templates/admin/users/users.html:9
 msgid "Users"
 msgstr ""
 
@@ -1720,8 +2416,12 @@ msgstr ""
 msgid "add new user"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_add.html:77
-#: rhodecode/templates/admin/users/user_edit.html:101
+#: rhodecode/templates/admin/users/user_add.html:50
+msgid "Password confirmation"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_add.html:86
+#: rhodecode/templates/admin/users/user_edit.html:113
 #: rhodecode/templates/admin/users_groups/users_group_add.html:41
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:42
 msgid "Active"
@@ -1731,36 +2431,93 @@ msgstr ""
 msgid "Edit user"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:33
-#: rhodecode/templates/admin/users/user_edit_my_account.html:32
+#: rhodecode/templates/admin/users/user_edit.html:34
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
 msgid "Change your avatar at"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:34
-#: rhodecode/templates/admin/users/user_edit_my_account.html:33
+#: rhodecode/templates/admin/users/user_edit.html:35
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
 msgid "Using"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:40
-#: rhodecode/templates/admin/users/user_edit_my_account.html:39
+#: rhodecode/templates/admin/users/user_edit.html:43
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
 msgid "API key"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:56
+#: rhodecode/templates/admin/users/user_edit.html:59
 msgid "LDAP DN"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:65
-#: rhodecode/templates/admin/users/user_edit_my_account.html:54
+#: rhodecode/templates/admin/users/user_edit.html:68
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
 msgid "New password"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:135
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:256
+#: rhodecode/templates/admin/users/user_edit.html:77
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
+msgid "New password confirmation"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:147
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:108
+msgid "Inherit default permissions"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:152
+#: 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 ""
+
+#: rhodecode/templates/admin/users/user_edit.html:158
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
 msgid "Create repositories"
 msgstr ""
 
+#: rhodecode/templates/admin/users/user_edit.html:166
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:127
+msgid "Fork repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:186
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
+msgid "Nothing here yet"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account.html:60
+#: rhodecode/templates/admin/users/user_edit_my_account.html:194
+msgid "Permission"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:194
+msgid "Edit Permission"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:243
+msgid "Email addresses"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:256
+#, python-format
+msgid "Confirm to delete this email: %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:270
+msgid "New email address"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:277
+msgid "Add"
+msgstr ""
+
 #: rhodecode/templates/admin/users/user_edit_my_account.html:5
+#: rhodecode/templates/base/base.html:124
 msgid "My account"
 msgstr ""
 
@@ -1768,26 +2525,74 @@ msgstr ""
 msgid "My Account"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:101
-msgid "My repositories"
-msgstr ""
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:107
-msgid "ADD REPOSITORY"
-msgstr ""
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:118
-#: rhodecode/templates/branches/branches_data.html:7
-#: rhodecode/templates/shortlog/shortlog_data.html:8
-#: rhodecode/templates/tags/tags_data.html:7
-msgid "revision"
-msgstr ""
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:35
+msgid "My permissions"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:38
+#: rhodecode/templates/journal/journal.html:41
+msgid "My repos"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:41
+msgid "My pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:45
+msgid "Add repo"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
+msgid "Opened by me"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
+#, python-format
+msgid "Pull request #%s opened on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
+msgid "Confirm to delete this pull request"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
+msgid "I participate in"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
+#, python-format
+msgid "Pull request #%s opened by %s on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
+#: rhodecode/templates/bookmarks/bookmarks.html:40
+#: rhodecode/templates/bookmarks/bookmarks_data.html:9
+#: rhodecode/templates/branches/branches.html:55
+#: rhodecode/templates/journal/journal.html:60
+#: rhodecode/templates/tags/tags.html:40
+#: rhodecode/templates/tags/tags_data.html:9
+msgid "Revision"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/journal/journal.html:81
+msgid "private"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
+#: rhodecode/templates/journal/journal.html:94
 msgid "No repositories yet"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
+#: rhodecode/templates/journal/journal.html:96
 msgid "create one now"
 msgstr ""
 
@@ -1795,42 +2600,41 @@ msgstr ""
 msgid "Users administration"
 msgstr ""
 
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/base/base.html:223
+msgid "users"
+msgstr ""
+
 #: rhodecode/templates/admin/users/users.html:23
 msgid "ADD NEW USER"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:33
+#: rhodecode/templates/admin/users/users.html:77
 msgid "username"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:34
-#: rhodecode/templates/branches/branches_data.html:5
-#: rhodecode/templates/tags/tags_data.html:5
-msgid "name"
-msgstr ""
-
-#: rhodecode/templates/admin/users/users.html:35
+#: rhodecode/templates/admin/users/users.html:80
+msgid "firstname"
+msgstr ""
+
+#: rhodecode/templates/admin/users/users.html:81
 msgid "lastname"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:36
+#: rhodecode/templates/admin/users/users.html:82
 msgid "last login"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:37
+#: rhodecode/templates/admin/users/users.html:84
 #: rhodecode/templates/admin/users_groups/users_groups.html:34
 msgid "active"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:39
-#: rhodecode/templates/base/base.html:305
+#: rhodecode/templates/admin/users/users.html:86
+#: rhodecode/templates/base/base.html:226
 msgid "ldap"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:56
-msgid "Confirm to delete this user"
-msgstr ""
-
 #: rhodecode/templates/admin/users_groups/users_group_add.html:5
 msgid "Add users group"
 msgstr ""
@@ -1872,6 +2676,10 @@ msgstr ""
 msgid "Add all elements"
 msgstr ""
 
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:146
+msgid "Group members"
+msgstr ""
+
 #: rhodecode/templates/admin/users_groups/users_groups.html:5
 msgid "Users groups administration"
 msgstr ""
@@ -1884,396 +2692,616 @@ msgstr ""
 msgid "group name"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:32
+#: rhodecode/templates/admin/users_groups/users_groups.html:33
+#: rhodecode/templates/base/root.html:46
+msgid "members"
+msgstr ""
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:45
+#, python-format
+msgid "Confirm to delete this users group: %s"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:41
+msgid "Submit a bug"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:77
+msgid "Login to your account"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:100
 msgid "Forgot password ?"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:57 rhodecode/templates/base/base.html:338
-#: rhodecode/templates/base/base.html:340
-#: rhodecode/templates/base/base.html:342
-msgid "Home"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:61 rhodecode/templates/base/base.html:347
-#: rhodecode/templates/base/base.html:349
-#: rhodecode/templates/base/base.html:351
-#: rhodecode/templates/journal/journal.html:4
-#: rhodecode/templates/journal/journal.html:17
-#: rhodecode/templates/journal/public_journal.html:4
-msgid "Journal"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:66
-msgid "Login"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:68
-msgid "Log Out"
-msgstr ""
-
 #: rhodecode/templates/base/base.html:107
-msgid "Submit a bug"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:141
+msgid "Log In"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:118
+msgid "Inbox"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:122
+#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/base/base.html:302
+#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/bookmarks/bookmarks.html:11
+#: rhodecode/templates/branches/branches.html:10
+#: rhodecode/templates/changelog/changelog.html:10
+#: rhodecode/templates/changeset/changeset.html:10
+#: rhodecode/templates/changeset/changeset_range.html:9
+#: rhodecode/templates/compare/compare_diff.html:9
+#: rhodecode/templates/files/file_diff.html:8
+#: rhodecode/templates/files/files.html:8
+#: 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/pullrequests/pullrequest.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
+#: rhodecode/templates/settings/repo_settings.html:9
+#: rhodecode/templates/shortlog/shortlog.html:10
+#: rhodecode/templates/summary/summary.html:8
+#: rhodecode/templates/tags/tags.html:11
+msgid "Home"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:123
+#: rhodecode/templates/base/base.html:309
+#: rhodecode/templates/base/base.html:311
+#: rhodecode/templates/base/base.html:313
+#: rhodecode/templates/journal/journal.html:4
+#: rhodecode/templates/journal/journal.html:21
+#: rhodecode/templates/journal/public_journal.html:4
+msgid "Journal"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:125
+msgid "Log Out"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:144
 msgid "Switch repository"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:143
+#: rhodecode/templates/base/base.html:146
 msgid "Products"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:149
+#: rhodecode/templates/base/base.html:152
+#: rhodecode/templates/base/base.html:182
 msgid "loading..."
 msgstr ""
 
-#: rhodecode/templates/base/base.html:234
-#: rhodecode/templates/base/base.html:236
-#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:158
+#: rhodecode/templates/base/base.html:160
+#: rhodecode/templates/base/base.html:162
+#: rhodecode/templates/data_table/_dt_elements.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:17
+#: rhodecode/templates/data_table/_dt_elements.html:19
+msgid "Summary"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:166
+#: rhodecode/templates/base/base.html:168
+#: rhodecode/templates/base/base.html:170
+#: rhodecode/templates/changelog/changelog.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:23
+#: rhodecode/templates/data_table/_dt_elements.html:25
+#: rhodecode/templates/data_table/_dt_elements.html:27
+msgid "Changelog"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:175
+#: rhodecode/templates/base/base.html:177
+#: rhodecode/templates/base/base.html:179
 msgid "Switch to"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:242
-#: rhodecode/templates/branches/branches.html:13
-msgid "branches"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:249
-#: rhodecode/templates/branches/branches_data.html:52
-msgid "There are no branches yet"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:254
-#: rhodecode/templates/shortlog/shortlog_data.html:10
-#: rhodecode/templates/tags/tags.html:14
-msgid "tags"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:261
-#: rhodecode/templates/tags/tags_data.html:32
-msgid "There are no tags yet"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:277
-#: rhodecode/templates/base/base.html:281
-#: rhodecode/templates/files/files_annotate.html:40
-#: rhodecode/templates/files/files_source.html:11
+#: rhodecode/templates/base/base.html:186
+#: rhodecode/templates/base/base.html:188
+#: rhodecode/templates/base/base.html:190
+#: rhodecode/templates/data_table/_dt_elements.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:33
+#: rhodecode/templates/data_table/_dt_elements.html:35
+msgid "Files"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:195
+#: rhodecode/templates/base/base.html:199
 msgid "Options"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:286
-#: rhodecode/templates/base/base.html:288
-#: rhodecode/templates/base/base.html:306
+#: rhodecode/templates/base/base.html:204
+#: rhodecode/templates/base/base.html:206
+#: rhodecode/templates/base/base.html:227
 msgid "settings"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:292
+#: rhodecode/templates/base/base.html:209
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/forks/fork.html:13
+msgid "fork"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:211
+#: rhodecode/templates/changelog/changelog.html:40
+msgid "Open new pull request"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:213
 msgid "search"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:299
-msgid "journal"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:301
+#: rhodecode/templates/base/base.html:222
 msgid "repositories groups"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:302
-msgid "users"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:303
+#: rhodecode/templates/base/base.html:224
 msgid "users groups"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/base/base.html:225
 msgid "permissions"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:317
-#: rhodecode/templates/base/base.html:319
-#: rhodecode/templates/followers/followers.html:5
+#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:240
 msgid "Followers"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:325
+#: rhodecode/templates/base/base.html:246
+#: rhodecode/templates/base/base.html:248
+msgid "Forks"
+msgstr ""
+
 #: rhodecode/templates/base/base.html:327
-#: rhodecode/templates/forks/forks.html:5
-msgid "Forks"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:356
-#: rhodecode/templates/base/base.html:358
-#: rhodecode/templates/base/base.html:360
-#: rhodecode/templates/search/search.html:4
-#: rhodecode/templates/search/search.html:24
-#: rhodecode/templates/search/search.html:46
+#: rhodecode/templates/base/base.html:329
+#: rhodecode/templates/base/base.html:331
+#: rhodecode/templates/search/search.html:52
 msgid "Search"
 msgstr ""
 
-#: rhodecode/templates/base/root.html:57
-#: rhodecode/templates/journal/journal.html:48
-#: rhodecode/templates/summary/summary.html:36
+#: rhodecode/templates/base/root.html:42
+msgid "add another comment"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:43
+#: rhodecode/templates/journal/journal.html:120
+#: rhodecode/templates/summary/summary.html:57
 msgid "Stop following this repository"
 msgstr ""
 
-#: rhodecode/templates/base/root.html:66
-#: rhodecode/templates/summary/summary.html:40
+#: rhodecode/templates/base/root.html:44
+#: rhodecode/templates/summary/summary.html:61
 msgid "Start following this repository"
 msgstr ""
 
-#: rhodecode/templates/branches/branches_data.html:4
-#: rhodecode/templates/tags/tags_data.html:4
-msgid "date"
+#: rhodecode/templates/base/root.html:45
+msgid "Group"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:47
+msgid "search truncated"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:48
+msgid "no matching files"
+msgstr ""
+
+#: rhodecode/templates/bookmarks/bookmarks.html:5
+#, python-format
+msgid "%s Bookmarks"
+msgstr ""
+
+#: rhodecode/templates/bookmarks/bookmarks.html:39
+#: rhodecode/templates/bookmarks/bookmarks_data.html:8
+#: rhodecode/templates/branches/branches.html:54
+#: rhodecode/templates/tags/tags.html:39
+#: rhodecode/templates/tags/tags_data.html:8
+msgid "Author"
+msgstr ""
+
+#: rhodecode/templates/branches/branches.html:5
+#, python-format
+msgid "%s Branches"
+msgstr ""
+
+#: rhodecode/templates/branches/branches.html:29
+msgid "Compare branches"
+msgstr ""
+
+#: rhodecode/templates/branches/branches.html:57
+#: rhodecode/templates/compare/compare_diff.html:5
+#: rhodecode/templates/compare/compare_diff.html:13
+msgid "Compare"
 msgstr ""
 
 #: rhodecode/templates/branches/branches_data.html:6
-#: rhodecode/templates/shortlog/shortlog_data.html:7
-#: rhodecode/templates/tags/tags_data.html:6
-msgid "author"
+msgid "name"
+msgstr ""
+
+#: rhodecode/templates/branches/branches_data.html:7
+msgid "date"
 msgstr ""
 
 #: rhodecode/templates/branches/branches_data.html:8
-#: rhodecode/templates/shortlog/shortlog_data.html:11
-#: rhodecode/templates/tags/tags_data.html:8
-msgid "links"
-msgstr ""
-
-#: rhodecode/templates/branches/branches_data.html:23
-#: rhodecode/templates/branches/branches_data.html:43
-#: rhodecode/templates/shortlog/shortlog_data.html:39
-#: rhodecode/templates/tags/tags_data.html:24
-msgid "changeset"
-msgstr ""
-
-#: rhodecode/templates/branches/branches_data.html:25
-#: rhodecode/templates/branches/branches_data.html:45
-#: rhodecode/templates/files/files.html:12
-#: rhodecode/templates/shortlog/shortlog_data.html:41
-#: rhodecode/templates/summary/summary.html:233
-#: rhodecode/templates/tags/tags_data.html:26
-msgid "files"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "showing "
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "out of"
+#: rhodecode/templates/shortlog/shortlog_data.html:8
+msgid "author"
+msgstr ""
+
+#: rhodecode/templates/branches/branches_data.html:9
+#: rhodecode/templates/shortlog/shortlog_data.html:5
+msgid "revision"
+msgstr ""
+
+#: rhodecode/templates/branches/branches_data.html:10
+msgid "compare"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:6
+#, python-format
+msgid "%s Changelog"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:15
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:19
+#, python-format
+msgid "compare fork with %s"
 msgstr ""
 
 #: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:21
+msgid "Compare fork"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:46
 msgid "Show"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:50
-#: rhodecode/templates/changeset/changeset.html:42
-#: rhodecode/templates/summary/summary.html:609
-msgid "commit"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:63
-msgid "Affected number of files, click to show more details"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:67
-#: rhodecode/templates/changeset/changeset.html:66
-msgid "merge"
-msgstr ""
-
 #: rhodecode/templates/changelog/changelog.html:72
-#: rhodecode/templates/changeset/changeset.html:72
+#: rhodecode/templates/summary/summary.html:364
+msgid "show more"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:76
+msgid "Affected number of files, click to show more details"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:89
+#: rhodecode/templates/changeset/changeset.html:38
+#: rhodecode/templates/changeset/changeset_file_comment.html:20
+#: rhodecode/templates/changeset/changeset_range.html:46
+msgid "Changeset status"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:92
+msgid "Click to open associated pull request"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:102
+#: rhodecode/templates/changeset/changeset.html:78
 msgid "Parent"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:77
-#: rhodecode/templates/changeset/changeset.html:77
+#: rhodecode/templates/changelog/changelog.html:108
+#: rhodecode/templates/changeset/changeset.html:84
 msgid "No parents"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:82
-#: rhodecode/templates/changeset/changeset.html:80
+#: rhodecode/templates/changelog/changelog.html:113
+#: rhodecode/templates/changeset/changeset.html:88
+msgid "merge"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:116
+#: rhodecode/templates/changeset/changeset.html:91
 #: rhodecode/templates/files/files.html:29
-#: rhodecode/templates/files/files_annotate.html:25
+#: rhodecode/templates/files/files_add.html:33
 #: rhodecode/templates/files/files_edit.html:33
 #: rhodecode/templates/shortlog/shortlog_data.html:9
 msgid "branch"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:86
-#: rhodecode/templates/changeset/changeset.html:83
+#: rhodecode/templates/changelog/changelog.html:122
+msgid "bookmark"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:128
+#: rhodecode/templates/changeset/changeset.html:96
 msgid "tag"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:122
+#: rhodecode/templates/changelog/changelog.html:164
 msgid "Show selected changes __S -> __E"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:172
-#: rhodecode/templates/shortlog/shortlog_data.html:61
+#: rhodecode/templates/changelog/changelog.html:255
 msgid "There are no changes yet"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog_details.html:2
-#: rhodecode/templates/changeset/changeset.html:55
-msgid "removed"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog_details.html:3
-#: rhodecode/templates/changeset/changeset.html:56
-msgid "changed"
-msgstr ""
-
 #: rhodecode/templates/changelog/changelog_details.html:4
-#: rhodecode/templates/changeset/changeset.html:57
-msgid "added"
+#: rhodecode/templates/changeset/changeset.html:66
+msgid "removed"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog_details.html:5
+#: rhodecode/templates/changeset/changeset.html:67
+msgid "changed"
 msgstr ""
 
 #: rhodecode/templates/changelog/changelog_details.html:6
-#: rhodecode/templates/changelog/changelog_details.html:7
+#: rhodecode/templates/changeset/changeset.html:68
+msgid "added"
+msgstr ""
+
 #: rhodecode/templates/changelog/changelog_details.html:8
-#: rhodecode/templates/changeset/changeset.html:59
-#: rhodecode/templates/changeset/changeset.html:60
-#: rhodecode/templates/changeset/changeset.html:61
+#: rhodecode/templates/changelog/changelog_details.html:9
+#: rhodecode/templates/changelog/changelog_details.html:10
+#: rhodecode/templates/changeset/changeset.html:70
+#: rhodecode/templates/changeset/changeset.html:71
+#: rhodecode/templates/changeset/changeset.html:72
 #, python-format
 msgid "affected %s files"
 msgstr ""
 
 #: rhodecode/templates/changeset/changeset.html:6
+#, python-format
+msgid "%s Changeset"
+msgstr ""
+
 #: rhodecode/templates/changeset/changeset.html:14
-#: rhodecode/templates/changeset/changeset.html:31
 msgid "Changeset"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:32
-#: rhodecode/templates/changeset/changeset.html:121
-#: rhodecode/templates/changeset/changeset_range.html:78
-#: rhodecode/templates/files/file_diff.html:32
-#: rhodecode/templates/files/file_diff.html:42
+#: rhodecode/templates/changeset/changeset.html:43
+#: rhodecode/templates/changeset/diff_block.html:20
 msgid "raw diff"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:34
-#: rhodecode/templates/changeset/changeset.html:123
-#: rhodecode/templates/changeset/changeset_range.html:80
-#: rhodecode/templates/files/file_diff.html:34
+#: rhodecode/templates/changeset/changeset.html:44
+#: rhodecode/templates/changeset/diff_block.html:21
 msgid "download diff"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:90
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
 #, python-format
-msgid "%s files affected with %s additions and %s deletions."
-msgstr ""
-
-#: rhodecode/templates/changeset/changeset.html:101
-msgid "Changeset was too big and was cut off..."
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "(%d inline)"
+msgid_plural "(%d inline)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/changeset/changeset.html:103
+#, python-format
+msgid "%s files affected with %s insertions and %s deletions:"
 msgstr ""
 
 #: rhodecode/templates/changeset/changeset.html:119
-#: rhodecode/templates/changeset/changeset_range.html:76
-#: rhodecode/templates/files/file_diff.html:30
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:42
+msgid "Submitting..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:45
+msgid "Commenting on line {1}."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:46
+#: rhodecode/templates/changeset/changeset_file_comment.html:121
+#, python-format
+msgid "Comments parsed using %s syntax with %s support."
+msgstr ""
+
+#: 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 ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:59
+#: rhodecode/templates/changeset/changeset_file_comment.html:138
+msgid "Comment"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:60
+#: rhodecode/templates/changeset/changeset_file_comment.html:71
+msgid "Hide"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "You need to be logged in to comment."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "Login now"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:118
+msgid "Leave a comment"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "Check this to change current status of code-review for this changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "change status"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:140
+msgid "Comment and close"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/templates/compare/compare_diff.html:29
+msgid "Compare View"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:54
+#: rhodecode/templates/compare/compare_diff.html:41
+#: rhodecode/templates/pullrequests/pullrequest_show.html:69
+msgid "Files affected"
+msgstr ""
+
+#: rhodecode/templates/changeset/diff_block.html:19
 msgid "diff"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:132
-#: rhodecode/templates/changeset/changeset_range.html:89
-msgid "No changes in this file"
-msgstr ""
-
-#: rhodecode/templates/changeset/changeset_range.html:30
-msgid "Compare View"
-msgstr ""
-
-#: rhodecode/templates/changeset/changeset_range.html:52
-msgid "Files affected"
-msgstr ""
-
-#: rhodecode/templates/errors/error_document.html:44
+#: rhodecode/templates/changeset/diff_block.html:27
+msgid "show inline comments"
+msgstr ""
+
+#: rhodecode/templates/compare/compare_cs.html:5
+msgid "No changesets"
+msgstr ""
+
+#: rhodecode/templates/compare/compare_diff.html:37
+msgid "Outgoing changesets"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:39
+#: rhodecode/templates/data_table/_dt_elements.html:41
+#: rhodecode/templates/data_table/_dt_elements.html:43
+msgid "Fork"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:60
+#: rhodecode/templates/journal/journal.html:126
+#: rhodecode/templates/summary/summary.html:68
+msgid "Mercurial repository"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:62
+#: rhodecode/templates/journal/journal.html:128
+#: rhodecode/templates/summary/summary.html:71
+msgid "Git repository"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:69
+#: rhodecode/templates/journal/journal.html:134
+#: rhodecode/templates/summary/summary.html:78
+msgid "public repository"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/summary/summary.html:87
+#: rhodecode/templates/summary/summary.html:88
+msgid "Fork of"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:92
+msgid "No changesets yet"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:104
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr ""
+
+#: rhodecode/templates/email_templates/main.html:8
+msgid "This is an notification from RhodeCode."
+msgstr ""
+
+#: rhodecode/templates/errors/error_document.html:46
 #, python-format
 msgid "You will be redirected to %s in %s seconds"
 msgstr ""
 
 #: rhodecode/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File diff"
+msgstr ""
+
 #: rhodecode/templates/files/file_diff.html:12
 msgid "File diff"
 msgstr ""
 
-#: rhodecode/templates/files/file_diff.html:42
-msgid "Diff is to big to display"
-msgstr ""
-
-#: rhodecode/templates/files/files.html:37
-#: rhodecode/templates/files/files_annotate.html:31
+#: rhodecode/templates/files/files.html:4
+#: rhodecode/templates/files/files.html:72
+#, python-format
+msgid "%s files"
+msgstr ""
+
+#: rhodecode/templates/files/files.html:12
+#: rhodecode/templates/summary/summary.html:340
+msgid "files"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:4
+#: rhodecode/templates/files/files_edit.html:4
+#, python-format
+msgid "%s Edit file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:19
+msgid "add file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:40
+msgid "Add new file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:45
+msgid "File Name"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:58
+msgid "or"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:54
+msgid "Upload file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:58
+msgid "Create new file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:63
 #: rhodecode/templates/files/files_edit.html:39
+#: rhodecode/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: rhodecode/templates/files/files.html:46
-msgid "Go back"
-msgstr ""
-
-#: rhodecode/templates/files/files.html:47
-msgid "No files at given path"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:4
-msgid "File annotate"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:12
-msgid "annotate"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:33
-#: rhodecode/templates/files/files_browser.html:160
-#: rhodecode/templates/files/files_source.html:2
-msgid "Revision"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:36
-#: rhodecode/templates/files/files_browser.html:158
-#: rhodecode/templates/files/files_source.html:7
-msgid "Size"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:38
-#: rhodecode/templates/files/files_browser.html:159
-#: rhodecode/templates/files/files_source.html:9
-msgid "Mimetype"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:41
-msgid "show source"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:43
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:14
-#: rhodecode/templates/files/files_source.html:51
-msgid "show as raw"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:45
-#: rhodecode/templates/files/files_source.html:16
-msgid "download as raw"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:54
-#: rhodecode/templates/files/files_source.html:25
-msgid "History"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:73
-#: rhodecode/templates/files/files_source.html:46
-#, python-format
-msgid "Binary file (%s)"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:51
-msgid "File is too big to display"
+#: rhodecode/templates/files/files_add.html:67
+msgid "use / to separate directories"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:77
+#: rhodecode/templates/files/files_edit.html:63
+#: rhodecode/templates/shortlog/shortlog_data.html:6
+msgid "commit message"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:81
+#: rhodecode/templates/files/files_edit.html:67
+msgid "Commit changes"
 msgstr ""
 
 #: rhodecode/templates/files/files_browser.html:13
@@ -2296,57 +3324,161 @@ msgstr ""
 msgid "search file list"
 msgstr ""
 
-#: rhodecode/templates/files/files_browser.html:32
+#: rhodecode/templates/files/files_browser.html:31
+#: rhodecode/templates/shortlog/shortlog_data.html:65
+msgid "add new file"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:35
 msgid "Loading file list..."
 msgstr ""
 
-#: rhodecode/templates/files/files_browser.html:111
-msgid "search truncated"
-msgstr ""
-
-#: rhodecode/templates/files/files_browser.html:122
-msgid "no matching files"
-msgstr ""
-
-#: rhodecode/templates/files/files_browser.html:161
+#: rhodecode/templates/files/files_browser.html:48
+msgid "Size"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:49
+msgid "Mimetype"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:50
+msgid "Last Revision"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:51
 msgid "Last modified"
 msgstr ""
 
-#: rhodecode/templates/files/files_browser.html:162
+#: rhodecode/templates/files/files_browser.html:52
 msgid "Last commiter"
 msgstr ""
 
-#: rhodecode/templates/files/files_edit.html:4
-msgid "Edit file"
-msgstr ""
-
 #: rhodecode/templates/files/files_edit.html:19
 msgid "edit file"
 msgstr ""
 
-#: rhodecode/templates/files/files_edit.html:45
-#: rhodecode/templates/shortlog/shortlog_data.html:5
-msgid "commit message"
+#: rhodecode/templates/files/files_edit.html:49
+#: rhodecode/templates/files/files_source.html:38
+msgid "show annotation"
+msgstr ""
+
+#: rhodecode/templates/files/files_edit.html:50
+#: rhodecode/templates/files/files_source.html:40
+#: rhodecode/templates/files/files_source.html:68
+msgid "show as raw"
 msgstr ""
 
 #: rhodecode/templates/files/files_edit.html:51
-msgid "Commit changes"
-msgstr ""
-
-#: rhodecode/templates/files/files_source.html:12
-msgid "show annotation"
-msgstr ""
-
-#: rhodecode/templates/files/files_source.html:153
+#: rhodecode/templates/files/files_source.html:41
+msgid "download as raw"
+msgstr ""
+
+#: rhodecode/templates/files/files_edit.html:54
+msgid "source"
+msgstr ""
+
+#: rhodecode/templates/files/files_edit.html:59
+msgid "Editing file"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:2
+msgid "History"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:9
+msgid "diff to revision"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:10
+#, fuzzy
+msgid "show at revision"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:14
+#, fuzzy, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/files/files_source.html:36
+msgid "show source"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:59
+#, python-format
+msgid "Binary file (%s)"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:68
+msgid "File is too big to display"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:124
 msgid "Selection link"
 msgstr ""
 
+#: rhodecode/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:15
+msgid "Go back"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:16
+msgid "No files at given path"
+msgstr ""
+
+#: rhodecode/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr ""
+
 #: rhodecode/templates/followers/followers.html:13
 msgid "followers"
 msgstr ""
 
 #: rhodecode/templates/followers/followers_data.html:12
-msgid "Started following"
+msgid "Started following -"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:5
+#, python-format
+msgid "%s Fork"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:31
+msgid "Fork name"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:68
+msgid "Private"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:77
+msgid "Copy permissions"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:81
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:86
+msgid "Update after clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:90
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:94
+msgid "fork this repository"
+msgstr ""
+
+#: rhodecode/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
 msgstr ""
 
 #: rhodecode/templates/forks/forks.html:13
@@ -2357,184 +3489,395 @@ msgstr ""
 msgid "forked"
 msgstr ""
 
-#: rhodecode/templates/forks/forks_data.html:34
+#: rhodecode/templates/forks/forks_data.html:38
 msgid "There are no forks yet"
 msgstr ""
 
-#: rhodecode/templates/journal/journal.html:34
-msgid "Following"
-msgstr ""
-
-#: rhodecode/templates/journal/journal.html:41
-msgid "following user"
+#: rhodecode/templates/journal/journal.html:13
+msgid "ATOM journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:14
+msgid "RSS journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:24
+#: rhodecode/templates/pullrequests/pullrequest.html:27
+msgid "Refresh"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:27
+#: rhodecode/templates/journal/public_journal.html:24
+msgid "RSS feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:30
+#: rhodecode/templates/journal/public_journal.html:27
+msgid "ATOM feed"
 msgstr ""
 
 #: rhodecode/templates/journal/journal.html:41
+msgid "Watched"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:46
+msgid "ADD"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "following user"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:114
 msgid "user"
 msgstr ""
 
-#: rhodecode/templates/journal/journal.html:65
+#: rhodecode/templates/journal/journal.html:147
 msgid "You are not following any users or repositories"
 msgstr ""
 
-#: rhodecode/templates/journal/journal_data.html:46
+#: rhodecode/templates/journal/journal_data.html:47
 msgid "No entries yet"
 msgstr ""
 
-#: rhodecode/templates/journal/public_journal.html:17
+#: rhodecode/templates/journal/public_journal.html:13
+msgid "ATOM public journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/public_journal.html:14
+msgid "RSS public journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/public_journal.html:21
 msgid "Public Journal"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:7
-#: rhodecode/templates/search/search.html:26
-msgid "in repository: "
-msgstr ""
-
-#: rhodecode/templates/search/search.html:9
-#: rhodecode/templates/search/search.html:28
-msgid "in all repositories"
-msgstr ""
-
-#: rhodecode/templates/search/search.html:42
+#: rhodecode/templates/pullrequests/pullrequest.html:4
+#: rhodecode/templates/pullrequests/pullrequest.html:12
+msgid "New pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:28
+msgid "refresh overview"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:66
+msgid "Detailed compare view"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:70
+#: rhodecode/templates/pullrequests/pullrequest_show.html:82
+msgid "Pull request reviewers"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:79
+#: rhodecode/templates/pullrequests/pullrequest_show.html:94
+msgid "owner"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:91
+#: rhodecode/templates/pullrequests/pullrequest_show.html:109
+msgid "Add reviewer to this pull request."
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:97
+msgid "Create new pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:106
+#: rhodecode/templates/pullrequests/pullrequest_show.html:25
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
+msgid "Title"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:115
+msgid "description"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:123
+msgid "Send pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:23
+#, python-format
+msgid "Closed %s"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:31
+msgid "Status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:36
+msgid "Pull request status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:44
+msgid "Still not reviewed by"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:47
+#, python-format
+msgid "%d reviewer"
+msgid_plural "%d reviewers"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:54
+msgid "Created on"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:61
+msgid "Compare view"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:65
+msgid "Incoming changesets"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
+msgid "all pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
+msgid "All pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
+msgid "Closed"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:6
+#, python-format
+msgid "Search \"%s\" in repository: %s"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:8
+#, python-format
+msgid "Search \"%s\" in all repositories"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:12
+#: rhodecode/templates/search/search.html:32
+#, python-format
+msgid "Search in repository: %s"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:14
+#: rhodecode/templates/search/search.html:34
+msgid "Search in all repositories"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:48
 msgid "Search term"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:54
+#: rhodecode/templates/search/search.html:60
 msgid "Search in"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:57
+#: rhodecode/templates/search/search.html:63
 msgid "File contents"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:59
+#: rhodecode/templates/search/search.html:64
+msgid "Commit messages"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:65
 msgid "File names"
 msgstr ""
 
-#: rhodecode/templates/search/search_content.html:20
+#: rhodecode/templates/search/search_commit.html:35
+#: rhodecode/templates/search/search_content.html:21
 #: rhodecode/templates/search/search_path.html:15
 msgid "Permission denied"
 msgstr ""
 
-#: rhodecode/templates/settings/repo_fork.html:5
-msgid "Fork"
-msgstr ""
-
-#: rhodecode/templates/settings/repo_fork.html:31
-msgid "Fork name"
-msgstr ""
-
-#: rhodecode/templates/settings/repo_fork.html:55
-msgid "fork this repository"
+#: rhodecode/templates/settings/repo_settings.html:5
+#, python-format
+msgid "%s Settings"
 msgstr ""
 
 #: rhodecode/templates/shortlog/shortlog.html:5
-#: rhodecode/templates/summary/summary.html:666
-msgid "Shortlog"
+#, python-format
+msgid "%s Shortlog"
 msgstr ""
 
 #: rhodecode/templates/shortlog/shortlog.html:14
 msgid "shortlog"
 msgstr ""
 
-#: rhodecode/templates/shortlog/shortlog_data.html:6
+#: rhodecode/templates/shortlog/shortlog_data.html:7
 msgid "age"
 msgstr ""
 
+#: rhodecode/templates/shortlog/shortlog_data.html:18
+msgid "No commit message"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:62
+msgid "Add or upload files directly via RhodeCode"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:71
+msgid "Push new repo"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:79
+msgid "Existing repository?"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:4
+#, python-format
+msgid "%s Summary"
+msgstr ""
+
 #: rhodecode/templates/summary/summary.html:12
 msgid "summary"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:79
+#: rhodecode/templates/summary/summary.html:20
+#, python-format
+msgid "repo %s ATOM feed"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:21
+#, python-format
+msgid "repo %s RSS feed"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:49
+#: rhodecode/templates/summary/summary.html:52
+msgid "ATOM"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:82
+#, python-format
+msgid "Non changable ID %s"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:87
+msgid "public"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:95
 msgid "remote clone"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:121
-msgid "by"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:128
+#: rhodecode/templates/summary/summary.html:116
+msgid "Contact"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:130
 msgid "Clone url"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:137
-msgid "Trending source files"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:146
-msgid "Download"
+#: rhodecode/templates/summary/summary.html:133
+msgid "Show by Name"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:134
+msgid "Show by ID"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:142
+msgid "Trending files"
 msgstr ""
 
 #: rhodecode/templates/summary/summary.html:150
-msgid "There are no downloads yet"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:152
-msgid "Downloads are disabled for this repository"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:154
-#: rhodecode/templates/summary/summary.html:320
+#: rhodecode/templates/summary/summary.html:166
+#: rhodecode/templates/summary/summary.html:194
 msgid "enable"
 msgstr ""
 
+#: rhodecode/templates/summary/summary.html:158
+msgid "Download"
+msgstr ""
+
 #: rhodecode/templates/summary/summary.html:162
-#: rhodecode/templates/summary/summary.html:297
+msgid "There are no downloads yet"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:164
+msgid "Downloads are disabled for this repository"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:170
+msgid "Download as zip"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:186
+msgid "Commit activity by day / author"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:197
+msgid "Stats gathered: "
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:218
+msgid "Shortlog"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:220
+msgid "Quick start"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:233
+#, python-format
+msgid "Readme file at revision '%s'"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:236
+msgid "Permalink to this readme"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:293
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:168
-msgid "Check this to download archive with subrepos"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:168
-msgid "with subrepos"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:176
-msgid "Feeds"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:257
-#: rhodecode/templates/summary/summary.html:684
-#: rhodecode/templates/summary/summary.html:695
-msgid "show more"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:312
-msgid "Commit activity by day / author"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:324
-msgid "Loaded in"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:603
+#: rhodecode/templates/summary/summary.html:650
 msgid "commits"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:604
+#: rhodecode/templates/summary/summary.html:651
 msgid "files added"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:605
+#: rhodecode/templates/summary/summary.html:652
 msgid "files changed"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:606
+#: rhodecode/templates/summary/summary.html:653
 msgid "files removed"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:610
+#: rhodecode/templates/summary/summary.html:656
+msgid "commit"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:657
 msgid "file added"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:611
+#: rhodecode/templates/summary/summary.html:658
 msgid "file changed"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:612
+#: rhodecode/templates/summary/summary.html:659
 msgid "file removed"
 msgstr ""
 
+#: rhodecode/templates/tags/tags.html:5
+#, python-format
+msgid "%s Tags"
+msgstr ""
+
diff --git a/rhodecode/i18n/fr/LC_MESSAGES/rhodecode.mo b/rhodecode/i18n/fr/LC_MESSAGES/rhodecode.mo
new file mode 100644
index 0000000000000000000000000000000000000000..c4d8e0f2fecf2ad5dd3d79d5b44c9ad7137233ef
GIT binary patch
literal 45107
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

diff --git a/rhodecode/i18n/fr/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/fr/LC_MESSAGES/rhodecode.po
new file mode 100644
--- /dev/null
+++ b/rhodecode/i18n/fr/LC_MESSAGES/rhodecode.po
@@ -0,0 +1,4047 @@
+# French translations for RhodeCode.
+# Copyright (C) 2011 ORGANIZATION
+# This file is distributed under the same license as the RhodeCode project.
+# FIRST AUTHOR , 2011.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: RhodeCode 1.1.5\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2012-09-02 20:30+0200\n"
+"PO-Revision-Date: 2012-06-05 20:07+0100\n"
+"Last-Translator: Vincent Duvert \n"
+"Language-Team: fr \n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\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"
+
+#: rhodecode/controllers/changelog.py:94
+msgid "All Branches"
+msgstr "Toutes les branches"
+
+#: rhodecode/controllers/changeset.py:83
+msgid "show white space"
+msgstr "afficher les espaces et tabulations"
+
+#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+msgid "ignore white space"
+msgstr "ignorer les espaces et tabulations"
+
+#: rhodecode/controllers/changeset.py:157
+#, python-format
+msgid "%s line context"
+msgstr "afficher %s lignes de contexte"
+
+#: rhodecode/controllers/changeset.py:333
+#: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
+msgid "binary file"
+msgstr "fichier binaire"
+
+#: rhodecode/controllers/changeset.py:408
+msgid ""
+"Changing status on a changeset associated witha closed pull request is "
+"not allowed"
+msgstr ""
+
+#: rhodecode/controllers/compare.py:69
+#, fuzzy
+msgid "There are no changesets yet"
+msgstr "Il n’y a aucun changement pour le moment"
+
+#: rhodecode/controllers/error.py:69
+msgid "Home page"
+msgstr "Accueil"
+
+#: rhodecode/controllers/error.py:98
+msgid "The request could not be understood by the server due to malformed syntax."
+msgstr ""
+"Le serveur n’a pas pu interpréter la requête à cause d’une erreur de "
+"syntaxe"
+
+#: rhodecode/controllers/error.py:101
+msgid "Unauthorized access to resource"
+msgstr "Accès interdit à cet ressource"
+
+#: rhodecode/controllers/error.py:103
+msgid "You don't have permission to view this page"
+msgstr "Vous n’avez pas la permission de voir cette page"
+
+#: rhodecode/controllers/error.py:105
+msgid "The resource could not be found"
+msgstr "Ressource introuvable"
+
+#: rhodecode/controllers/error.py:107
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+"La requête n’a pu être traitée en raison d’une erreur survenue sur le "
+"serveur."
+
+#: rhodecode/controllers/feed.py:49
+#, python-format
+msgid "Changes on %s repository"
+msgstr "Changements sur le dépôt %s"
+
+#: rhodecode/controllers/feed.py:50
+#, python-format
+msgid "%s %s feed"
+msgstr "Flux %s de %s"
+
+#: rhodecode/controllers/feed.py:75
+msgid "commited on"
+msgstr "a commité, le"
+
+#: rhodecode/controllers/files.py:84
+#, fuzzy
+msgid "click here to add new file"
+msgstr "Ajouter un fichier"
+
+#: rhodecode/controllers/files.py:85
+#, python-format
+msgid "There are no files yet %s"
+msgstr "Il n’y a pas encore de fichiers %s"
+
+#: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
+#, python-format
+msgid "This repository is has been locked by %s on %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:266
+#, python-format
+msgid "Edited %s via RhodeCode"
+msgstr "%s édité via RhodeCode"
+
+#: rhodecode/controllers/files.py:271
+msgid "No changes"
+msgstr "Aucun changement"
+
+#: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
+#, python-format
+msgid "Successfully committed to %s"
+msgstr "Commit réalisé avec succès sur %s"
+
+#: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
+msgid "Error occurred during commit"
+msgstr "Une erreur est survenue durant le commit"
+
+#: rhodecode/controllers/files.py:318
+#, python-format
+msgid "Added %s via RhodeCode"
+msgstr "%s ajouté par RhodeCode"
+
+#: rhodecode/controllers/files.py:332
+msgid "No content"
+msgstr "Aucun contenu"
+
+#: rhodecode/controllers/files.py:336
+msgid "No filename"
+msgstr "Aucun nom de fichier"
+
+#: rhodecode/controllers/files.py:378
+msgid "downloads disabled"
+msgstr "Les téléchargements sont désactivés"
+
+#: rhodecode/controllers/files.py:389
+#, python-format
+msgid "Unknown revision %s"
+msgstr "Révision %s inconnue."
+
+#: rhodecode/controllers/files.py:391
+msgid "Empty repository"
+msgstr "Dépôt vide."
+
+#: rhodecode/controllers/files.py:393
+msgid "Unknown archive type"
+msgstr "Type d’archive inconnu"
+
+#: rhodecode/controllers/files.py:494
+#: rhodecode/templates/changeset/changeset_range.html:13
+#: rhodecode/templates/changeset/changeset_range.html:31
+msgid "Changesets"
+msgstr "Changesets"
+
+#: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
+#: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
+msgid "Branches"
+msgstr "Branches"
+
+#: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
+#: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
+msgid "Tags"
+msgstr "Tags"
+
+#: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
+#, 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 ""
+"Le dépôt %s n’est pas représenté dans la base de données. Il a "
+"probablement été créé ou renommé manuellement. Veuillez relancer "
+"l’application pour rescanner les dépôts."
+
+#: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
+#, python-format
+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 ""
+"Le dépôt %s n’est pas représenté dans la base de données. Il a "
+"probablement été créé ou renommé manuellement. Veuillez relancer "
+"l’application pour rescanner les dépôts."
+
+#: rhodecode/controllers/forks.py:167
+#, python-format
+msgid "forked %s repository as %s"
+msgstr "dépôt %s forké en tant que %s"
+
+#: rhodecode/controllers/forks.py:181
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr "Une erreur est survenue durant le fork du dépôt %s."
+
+#: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
+#, fuzzy
+msgid "public journal"
+msgstr "Journal public"
+
+#: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
+#: rhodecode/templates/base/base.html:220
+msgid "journal"
+msgstr "Journal"
+
+#: rhodecode/controllers/login.py:143
+msgid "You have successfully registered into rhodecode"
+msgstr "Vous vous êtes inscrits avec succès à RhodeCode"
+
+#: rhodecode/controllers/login.py:164
+msgid "Your password reset link was sent"
+msgstr "Un lien de rénitialisation de votre mot de passe vous a été envoyé."
+
+#: rhodecode/controllers/login.py:184
+msgid ""
+"Your password reset was successful, new password has been sent to your "
+"email"
+msgstr ""
+"Votre mot de passe a été réinitialisé. Votre nouveau mot de passe vous a "
+"été envoyé par e-mail."
+
+#: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
+#, fuzzy
+msgid "Bookmarks"
+msgstr "Signets"
+
+#: rhodecode/controllers/pullrequests.py:158
+msgid "Pull request requires a title with min. 3 chars"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:160
+msgid "error during creation of pull request"
+msgstr "erreur lors de la création de la demande traction"
+
+#: rhodecode/controllers/pullrequests.py:181
+#, fuzzy
+msgid "Successfully opened new pull request"
+msgstr "L’utilisateur a été supprimé avec succès."
+
+#: rhodecode/controllers/pullrequests.py:184
+#, fuzzy
+msgid "Error occurred during sending pull request"
+msgstr "Une erreur est survenue durant la création du dépôt %s."
+
+#: rhodecode/controllers/pullrequests.py:217
+#, fuzzy
+msgid "Successfully deleted pull request"
+msgstr "L’utilisateur a été supprimé avec succès."
+
+#: rhodecode/controllers/search.py:131
+msgid "Invalid search query. Try quoting it."
+msgstr "Requête invalide. Essayer de la mettre entre guillemets."
+
+#: rhodecode/controllers/search.py:136
+msgid "There is no index to search in. Please run whoosh indexer"
+msgstr ""
+"L’index de recherche n’est pas présent. Veuillez exécuter l’indexeur de "
+"code Whoosh."
+
+#: rhodecode/controllers/search.py:140
+msgid "An error occurred during this search operation"
+msgstr "Une erreur est survenue durant l’opération de recherche."
+
+#: rhodecode/controllers/settings.py:107
+#: rhodecode/controllers/admin/repos.py:266
+#, python-format
+msgid "Repository %s updated successfully"
+msgstr "Dépôt %s mis à jour avec succès."
+
+#: rhodecode/controllers/settings.py:125
+#: rhodecode/controllers/admin/repos.py:284
+#, python-format
+msgid "error occurred during update of repository %s"
+msgstr "Une erreur est survenue lors de la mise à jour du dépôt %s."
+
+#: rhodecode/controllers/settings.py:143
+#: rhodecode/controllers/admin/repos.py:302
+#, python-format
+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 ""
+"Le dépôt %s n’est pas représenté dans la base de données. Il a "
+"probablement été déplacé ou renommé manuellement. Veuillez relancer "
+"l’application pour rescanner les dépôts."
+
+#: rhodecode/controllers/settings.py:155
+#: rhodecode/controllers/admin/repos.py:314
+#, python-format
+msgid "deleted repository %s"
+msgstr "Dépôt %s supprimé"
+
+#: rhodecode/controllers/settings.py:159
+#: rhodecode/controllers/admin/repos.py:324
+#: rhodecode/controllers/admin/repos.py:330
+#, python-format
+msgid "An error occurred during deletion of %s"
+msgstr "Erreur pendant la suppression de %s"
+
+#: rhodecode/controllers/summary.py:138
+msgid "No data loaded yet"
+msgstr "Aucune donnée actuellement disponible."
+
+#: rhodecode/controllers/summary.py:142
+#: rhodecode/templates/summary/summary.html:148
+msgid "Statistics are disabled for this repository"
+msgstr "La mise à jour des statistiques est désactivée pour ce dépôt."
+
+#: rhodecode/controllers/admin/ldap_settings.py:50
+msgid "BASE"
+msgstr "Base"
+
+#: rhodecode/controllers/admin/ldap_settings.py:51
+msgid "ONELEVEL"
+msgstr "Un niveau"
+
+#: rhodecode/controllers/admin/ldap_settings.py:52
+msgid "SUBTREE"
+msgstr "Sous-arbre"
+
+#: rhodecode/controllers/admin/ldap_settings.py:56
+msgid "NEVER"
+msgstr "NEVER"
+
+#: rhodecode/controllers/admin/ldap_settings.py:57
+msgid "ALLOW"
+msgstr "Autoriser"
+
+#: rhodecode/controllers/admin/ldap_settings.py:58
+msgid "TRY"
+msgstr "TRY"
+
+#: rhodecode/controllers/admin/ldap_settings.py:59
+msgid "DEMAND"
+msgstr "DEMAND"
+
+#: rhodecode/controllers/admin/ldap_settings.py:60
+msgid "HARD"
+msgstr "HARD"
+
+#: rhodecode/controllers/admin/ldap_settings.py:64
+msgid "No encryption"
+msgstr "Pas de chiffrement"
+
+#: rhodecode/controllers/admin/ldap_settings.py:65
+msgid "LDAPS connection"
+msgstr "Connection LDAPS"
+
+#: rhodecode/controllers/admin/ldap_settings.py:66
+msgid "START_TLS on LDAP connection"
+msgstr "START_TLS à la connexion"
+
+#: rhodecode/controllers/admin/ldap_settings.py:126
+msgid "Ldap settings updated successfully"
+msgstr "Mise à jour réussie des réglages LDAP"
+
+#: rhodecode/controllers/admin/ldap_settings.py:130
+msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
+msgstr "Impossible d’activer LDAP. La bibliothèque « python-ldap » est manquante."
+
+#: rhodecode/controllers/admin/ldap_settings.py:147
+msgid "error occurred during update of ldap settings"
+msgstr "Une erreur est survenue durant la mise à jour des réglages du LDAP."
+
+#: rhodecode/controllers/admin/permissions.py:59
+msgid "None"
+msgstr "Aucun"
+
+#: rhodecode/controllers/admin/permissions.py:60
+msgid "Read"
+msgstr "Lire"
+
+#: rhodecode/controllers/admin/permissions.py:61
+msgid "Write"
+msgstr "Écrire"
+
+#: rhodecode/controllers/admin/permissions.py:62
+#: rhodecode/templates/admin/ldap/ldap.html:9
+#: rhodecode/templates/admin/permissions/permissions.html:9
+#: rhodecode/templates/admin/repos/repo_add.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:9
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
+#: rhodecode/templates/admin/settings/hooks.html:9
+#: rhodecode/templates/admin/settings/settings.html:9
+#: rhodecode/templates/admin/users/user_add.html:8
+#: rhodecode/templates/admin/users/user_edit.html:9
+#: rhodecode/templates/admin/users/user_edit.html:122
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/admin/users_groups/users_group_add.html:8
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:9
+#: rhodecode/templates/admin/users_groups/users_groups.html:9
+#: rhodecode/templates/base/base.html:197
+#: rhodecode/templates/base/base.html:337
+#: rhodecode/templates/base/base.html:339
+#: rhodecode/templates/base/base.html:341
+msgid "Admin"
+msgstr "Administration"
+
+#: rhodecode/controllers/admin/permissions.py:65
+msgid "disabled"
+msgstr "Désactivé"
+
+#: rhodecode/controllers/admin/permissions.py:67
+msgid "allowed with manual account activation"
+msgstr "Autorisé avec activation manuelle du compte"
+
+#: rhodecode/controllers/admin/permissions.py:69
+msgid "allowed with automatic account activation"
+msgstr "Autorisé avec activation automatique du compte"
+
+#: rhodecode/controllers/admin/permissions.py:71
+#: rhodecode/controllers/admin/permissions.py:74
+msgid "Disabled"
+msgstr "Interdite"
+
+#: rhodecode/controllers/admin/permissions.py:72
+#: rhodecode/controllers/admin/permissions.py:75
+msgid "Enabled"
+msgstr "Autorisée"
+
+#: rhodecode/controllers/admin/permissions.py:116
+msgid "Default permissions updated successfully"
+msgstr "Permissions par défaut mises à jour avec succès"
+
+#: rhodecode/controllers/admin/permissions.py:130
+msgid "error occurred during update of permissions"
+msgstr "erreur pendant la mise à jour des permissions"
+
+#: rhodecode/controllers/admin/repos.py:123
+msgid "--REMOVE FORK--"
+msgstr "[Pas un fork]"
+
+#: rhodecode/controllers/admin/repos.py:192
+#, python-format
+msgid "created repository %s from %s"
+msgstr "Le dépôt %s a été créé depuis %s."
+
+#: rhodecode/controllers/admin/repos.py:196
+#, python-format
+msgid "created repository %s"
+msgstr "Le dépôt %s a été créé."
+
+#: rhodecode/controllers/admin/repos.py:227
+#, python-format
+msgid "error occurred during creation of repository %s"
+msgstr "Une erreur est survenue durant la création du dépôt %s."
+
+#: rhodecode/controllers/admin/repos.py:319
+#, python-format
+msgid "Cannot delete %s it still contains attached forks"
+msgstr "Impossible de supprimer le dépôt %s : Des forks y sont attachés."
+
+#: rhodecode/controllers/admin/repos.py:348
+msgid "An error occurred during deletion of repository user"
+msgstr "Une erreur est survenue durant la suppression de l’utilisateur du dépôt."
+
+#: rhodecode/controllers/admin/repos.py:367
+msgid "An error occurred during deletion of repository users groups"
+msgstr ""
+"Une erreur est survenue durant la suppression du groupe d’utilisateurs de"
+" ce dépôt."
+
+#: rhodecode/controllers/admin/repos.py:385
+msgid "An error occurred during deletion of repository stats"
+msgstr "Une erreur est survenue durant la suppression des statistiques du dépôt."
+
+#: rhodecode/controllers/admin/repos.py:402
+msgid "An error occurred during cache invalidation"
+msgstr "Une erreur est survenue durant l’invalidation du cache."
+
+#: rhodecode/controllers/admin/repos.py:422
+#, fuzzy
+msgid "An error occurred during unlocking"
+msgstr "Une erreur est survenue durant cette opération."
+
+#: rhodecode/controllers/admin/repos.py:442
+msgid "Updated repository visibility in public journal"
+msgstr "La visibilité du dépôt dans le journal public a été mise à jour."
+
+#: rhodecode/controllers/admin/repos.py:446
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+"Une erreur est survenue durant la configuration du journal public pour ce"
+" dépôt."
+
+#: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
+msgid "Token mismatch"
+msgstr "Jeton d’authentification incorrect."
+
+#: rhodecode/controllers/admin/repos.py:464
+msgid "Pulled from remote location"
+msgstr "Les changements distants ont été récupérés."
+
+#: rhodecode/controllers/admin/repos.py:466
+msgid "An error occurred during pull from remote location"
+msgstr "Une erreur est survenue durant le pull depuis la source distante."
+
+#: rhodecode/controllers/admin/repos.py:482
+msgid "Nothing"
+msgstr "[Aucun dépôt]"
+
+#: rhodecode/controllers/admin/repos.py:484
+#, python-format
+msgid "Marked repo %s as fork of %s"
+msgstr "Le dépôt %s a été marké comme fork de %s"
+
+#: rhodecode/controllers/admin/repos.py:488
+msgid "An error occurred during this operation"
+msgstr "Une erreur est survenue durant cette opération."
+
+#: rhodecode/controllers/admin/repos_groups.py:116
+#, python-format
+msgid "created repos group %s"
+msgstr "Le groupe de dépôts %s a été créé."
+
+#: rhodecode/controllers/admin/repos_groups.py:129
+#, python-format
+msgid "error occurred during creation of repos group %s"
+msgstr "Une erreur est survenue durant la création du groupe de dépôts %s."
+
+#: rhodecode/controllers/admin/repos_groups.py:163
+#, python-format
+msgid "updated repos group %s"
+msgstr "Le groupe de dépôts %s a été mis à jour."
+
+#: rhodecode/controllers/admin/repos_groups.py:176
+#, python-format
+msgid "error occurred during update of repos group %s"
+msgstr "Une erreur est survenue durant la mise à jour du groupe de dépôts %s."
+
+#: rhodecode/controllers/admin/repos_groups.py:194
+#, python-format
+msgid "This group contains %s repositores and cannot be deleted"
+msgstr "Ce groupe contient %s dépôts et ne peut être supprimé."
+
+#: rhodecode/controllers/admin/repos_groups.py:202
+#, python-format
+msgid "removed repos group %s"
+msgstr "Le groupe de dépôts %s a été supprimé."
+
+#: rhodecode/controllers/admin/repos_groups.py:208
+msgid "Cannot delete this group it still contains subgroups"
+msgstr "Impossible de supprimer ce groupe : Il contient des sous-groupes."
+
+#: rhodecode/controllers/admin/repos_groups.py:213
+#: rhodecode/controllers/admin/repos_groups.py:218
+#, python-format
+msgid "error occurred during deletion of repos group %s"
+msgstr "Une erreur est survenue durant la suppression du groupe de dépôts %s."
+
+#: rhodecode/controllers/admin/repos_groups.py:238
+msgid "An error occurred during deletion of group user"
+msgstr ""
+"Une erreur est survenue durant la suppression de l’utilisateur du groupe "
+"de dépôts."
+
+#: rhodecode/controllers/admin/repos_groups.py:258
+msgid "An error occurred during deletion of group users groups"
+msgstr ""
+"Une erreur est survenue durant la suppression du groupe d’utilisateurs du"
+" groupe de dépôts."
+
+#: rhodecode/controllers/admin/settings.py:121
+#, python-format
+msgid "Repositories successfully rescanned added: %s,removed: %s"
+msgstr "Après re-scan : %s ajouté(s), %s enlevé(s)"
+
+#: rhodecode/controllers/admin/settings.py:129
+msgid "Whoosh reindex task scheduled"
+msgstr "La tâche de réindexation Whoosh a été planifiée."
+
+#: rhodecode/controllers/admin/settings.py:160
+msgid "Updated application settings"
+msgstr "Réglages mis à jour"
+
+#: rhodecode/controllers/admin/settings.py:164
+#: rhodecode/controllers/admin/settings.py:275
+msgid "error occurred during updating application settings"
+msgstr "Une erreur est survenue durant la mise à jour des options."
+
+#: rhodecode/controllers/admin/settings.py:200
+#, fuzzy
+msgid "Updated visualisation settings"
+msgstr "Réglages mis à jour"
+
+#: rhodecode/controllers/admin/settings.py:205
+#, fuzzy
+msgid "error occurred during updating visualisation settings"
+msgstr "Une erreur est survenue durant la mise à jour des options."
+
+#: rhodecode/controllers/admin/settings.py:271
+#, fuzzy
+msgid "Updated VCS settings"
+msgstr "Réglages de Mercurial mis à jour"
+
+#: rhodecode/controllers/admin/settings.py:285
+msgid "Added new hook"
+msgstr "Le nouveau hook a été ajouté."
+
+#: rhodecode/controllers/admin/settings.py:297
+msgid "Updated hooks"
+msgstr "Hooks mis à jour"
+
+#: rhodecode/controllers/admin/settings.py:301
+msgid "error occurred during hook creation"
+msgstr "Une erreur est survenue durant la création du hook."
+
+#: rhodecode/controllers/admin/settings.py:320
+msgid "Email task created"
+msgstr "La tâche d’e-mail a été créée."
+
+#: rhodecode/controllers/admin/settings.py:375
+msgid "You can't edit this user since it's crucial for entire application"
+msgstr ""
+"Vous ne pouvez pas éditer cet utilisateur ; il est nécessaire pour le bon"
+" fonctionnement de l’application."
+
+#: rhodecode/controllers/admin/settings.py:406
+msgid "Your account was updated successfully"
+msgstr "Votre compte a été mis à jour avec succès"
+
+#: rhodecode/controllers/admin/settings.py:421
+#: rhodecode/controllers/admin/users.py:191
+#, python-format
+msgid "error occurred during update of user %s"
+msgstr "Une erreur est survenue durant la mise à jour de l’utilisateur %s."
+
+#: rhodecode/controllers/admin/users.py:130
+#, python-format
+msgid "created user %s"
+msgstr "utilisateur %s créé"
+
+#: rhodecode/controllers/admin/users.py:142
+#, python-format
+msgid "error occurred during creation of user %s"
+msgstr "Une erreur est survenue durant la création de l’utilisateur %s."
+
+#: rhodecode/controllers/admin/users.py:171
+msgid "User updated successfully"
+msgstr "L’utilisateur a été mis à jour avec succès."
+
+#: rhodecode/controllers/admin/users.py:207
+msgid "successfully deleted user"
+msgstr "L’utilisateur a été supprimé avec succès."
+
+#: rhodecode/controllers/admin/users.py:212
+msgid "An error occurred during deletion of user"
+msgstr "Une erreur est survenue durant la suppression de l’utilisateur."
+
+#: rhodecode/controllers/admin/users.py:226
+msgid "You can't edit this user"
+msgstr "Vous ne pouvez pas éditer cet utilisateur"
+
+#: rhodecode/controllers/admin/users.py:266
+msgid "Granted 'repository create' permission to user"
+msgstr "La permission de création de dépôts a été accordée à l’utilisateur."
+
+#: rhodecode/controllers/admin/users.py:271
+msgid "Revoked 'repository create' permission to user"
+msgstr "La permission de création de dépôts a été révoquée à l’utilisateur."
+
+#: rhodecode/controllers/admin/users.py:277
+#, fuzzy
+msgid "Granted 'repository fork' permission to user"
+msgstr "La permission de création de dépôts a été accordée à l’utilisateur."
+
+#: rhodecode/controllers/admin/users.py:282
+#, fuzzy
+msgid "Revoked 'repository fork' permission to user"
+msgstr "La permission de création de dépôts a été révoquée à l’utilisateur."
+
+#: rhodecode/controllers/admin/users.py:288
+#: rhodecode/controllers/admin/users_groups.py:255
+#, fuzzy
+msgid "An error occurred during permissions saving"
+msgstr "Une erreur est survenue durant cette opération."
+
+#: rhodecode/controllers/admin/users.py:303
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:309
+#, fuzzy
+msgid "An error occurred during email saving"
+msgstr "Une erreur est survenue durant cette opération."
+
+#: rhodecode/controllers/admin/users.py:319
+#, fuzzy
+msgid "Removed email from user"
+msgstr "Le groupe de dépôts %s a été supprimé."
+
+#: rhodecode/controllers/admin/users_groups.py:84
+#, python-format
+msgid "created users group %s"
+msgstr "Le groupe d’utilisateurs %s a été créé."
+
+#: rhodecode/controllers/admin/users_groups.py:95
+#, python-format
+msgid "error occurred during creation of users group %s"
+msgstr "Une erreur est survenue durant la création du groupe d’utilisateurs %s."
+
+#: rhodecode/controllers/admin/users_groups.py:135
+#, python-format
+msgid "updated users group %s"
+msgstr "Le groupe d’utilisateurs %s a été mis à jour."
+
+#: rhodecode/controllers/admin/users_groups.py:157
+#, python-format
+msgid "error occurred during update of users group %s"
+msgstr "Une erreur est survenue durant la mise à jour du groupe d’utilisateurs %s."
+
+#: rhodecode/controllers/admin/users_groups.py:174
+msgid "successfully deleted users group"
+msgstr "Le groupe d’utilisateurs a été supprimé avec succès."
+
+#: rhodecode/controllers/admin/users_groups.py:179
+msgid "An error occurred during deletion of users group"
+msgstr "Une erreur est survenue lors de la suppression du groupe d’utilisateurs."
+
+#: rhodecode/controllers/admin/users_groups.py:233
+#, fuzzy
+msgid "Granted 'repository create' permission to users group"
+msgstr "La permission de création de dépôts a été accordée à l’utilisateur."
+
+#: rhodecode/controllers/admin/users_groups.py:238
+#, fuzzy
+msgid "Revoked 'repository create' permission to users group"
+msgstr "La permission de création de dépôts a été révoquée à l’utilisateur."
+
+#: rhodecode/controllers/admin/users_groups.py:244
+#, fuzzy
+msgid "Granted 'repository fork' permission to users group"
+msgstr "La permission de création de dépôts a été accordée à l’utilisateur."
+
+#: rhodecode/controllers/admin/users_groups.py:249
+#, fuzzy
+msgid "Revoked 'repository fork' permission to users group"
+msgstr "La permission de création de dépôts a été révoquée à l’utilisateur."
+
+#: rhodecode/lib/auth.py:499
+msgid "You need to be a registered user to perform this action"
+msgstr "Vous devez être un utilisateur enregistré pour effectuer cette action."
+
+#: rhodecode/lib/auth.py:540
+msgid "You need to be a signed in to view this page"
+msgstr "Vous devez être connecté pour visualiser cette page."
+
+#: rhodecode/lib/diffs.py:86
+msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+"Cet ensemble de changements était trop gros pour être affiché et a été "
+"découpé, utilisez le menu « Diff » pour afficher les différences."
+
+#: rhodecode/lib/diffs.py:96
+msgid "No changes detected"
+msgstr "Aucun changement détecté."
+
+#: rhodecode/lib/helpers.py:372
+#, python-format
+msgid "%a, %d %b %Y %H:%M:%S"
+msgstr "%d/%m/%Y à %H:%M:%S"
+
+#: rhodecode/lib/helpers.py:484
+msgid "True"
+msgstr "Vrai"
+
+#: rhodecode/lib/helpers.py:488
+msgid "False"
+msgstr "Faux"
+
+#: rhodecode/lib/helpers.py:532
+msgid "Changeset not found"
+msgstr "Ensemble de changements non trouvé"
+
+#: rhodecode/lib/helpers.py:555
+#, python-format
+msgid "Show all combined changesets %s->%s"
+msgstr "Afficher les changements combinés %s->%s"
+
+#: rhodecode/lib/helpers.py:561
+msgid "compare view"
+msgstr "vue de comparaison"
+
+#: rhodecode/lib/helpers.py:581
+msgid "and"
+msgstr "et"
+
+#: rhodecode/lib/helpers.py:582
+#, python-format
+msgid "%s more"
+msgstr "%s de plus"
+
+#: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
+msgid "revisions"
+msgstr "révisions"
+
+#: rhodecode/lib/helpers.py:606
+msgid "fork name "
+msgstr "Nom du fork"
+
+#: rhodecode/lib/helpers.py:620
+#: rhodecode/templates/pullrequests/pullrequest_show.html:4
+#: rhodecode/templates/pullrequests/pullrequest_show.html:12
+#, python-format
+msgid "Pull request #%s"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:626
+msgid "[deleted] repository"
+msgstr "[a supprimé] le dépôt"
+
+#: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
+msgid "[created] repository"
+msgstr "[a créé] le dépôt"
+
+#: rhodecode/lib/helpers.py:630
+msgid "[created] repository as fork"
+msgstr "[a créé] le dépôt en tant que fork"
+
+#: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
+msgid "[forked] repository"
+msgstr "[a forké] le dépôt"
+
+#: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
+msgid "[updated] repository"
+msgstr "[a mis à jour] le dépôt"
+
+#: rhodecode/lib/helpers.py:636
+msgid "[delete] repository"
+msgstr "[a supprimé] le dépôt"
+
+#: rhodecode/lib/helpers.py:644
+msgid "[created] user"
+msgstr "[a créé] l’utilisateur"
+
+#: rhodecode/lib/helpers.py:646
+msgid "[updated] user"
+msgstr "[a mis à jour] l’utilisateur"
+
+#: rhodecode/lib/helpers.py:648
+msgid "[created] users group"
+msgstr "[a créé] le groupe d’utilisateurs"
+
+#: rhodecode/lib/helpers.py:650
+msgid "[updated] users group"
+msgstr "[a mis à jour] le groupe d’utilisateurs"
+
+#: rhodecode/lib/helpers.py:652
+msgid "[commented] on revision in repository"
+msgstr "[a commenté] une révision du dépôt"
+
+#: rhodecode/lib/helpers.py:654
+#, fuzzy
+msgid "[commented] on pull request for"
+msgstr "[a commenté] une révision du dépôt"
+
+#: rhodecode/lib/helpers.py:656
+#, fuzzy
+msgid "[closed] pull request for"
+msgstr "[a commenté] une révision du dépôt"
+
+#: rhodecode/lib/helpers.py:658
+msgid "[pushed] into"
+msgstr "[a pushé] dans"
+
+#: rhodecode/lib/helpers.py:660
+msgid "[committed via RhodeCode] into repository"
+msgstr "[a commité via RhodeCode] dans le dépôt"
+
+#: rhodecode/lib/helpers.py:662
+msgid "[pulled from remote] into repository"
+msgstr "[a pullé depuis un site distant] dans le dépôt"
+
+#: rhodecode/lib/helpers.py:664
+msgid "[pulled] from"
+msgstr "[a pullé] depuis"
+
+#: rhodecode/lib/helpers.py:666
+msgid "[started following] repository"
+msgstr "[suit maintenant] le dépôt"
+
+#: rhodecode/lib/helpers.py:668
+msgid "[stopped following] repository"
+msgstr "[ne suit plus] le dépôt"
+
+#: rhodecode/lib/helpers.py:840
+#, python-format
+msgid " and %s more"
+msgstr "et %s de plus"
+
+#: rhodecode/lib/helpers.py:844
+msgid "No Files"
+msgstr "Aucun fichier"
+
+#: rhodecode/lib/utils2.py:335
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d an"
+msgstr[1] "%d ans"
+
+#: rhodecode/lib/utils2.py:336
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mois"
+msgstr[1] "%d mois"
+
+#: rhodecode/lib/utils2.py:337
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d jour"
+msgstr[1] "%d jours"
+
+#: rhodecode/lib/utils2.py:338
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d heure"
+msgstr[1] "%d heures"
+
+#: rhodecode/lib/utils2.py:339
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minute"
+msgstr[1] "%d minutes"
+
+#: rhodecode/lib/utils2.py:340
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] "%d seconde"
+msgstr[1] "%d secondes"
+
+#: rhodecode/lib/utils2.py:355
+#, python-format
+msgid "%s ago"
+msgstr "Il y a %s"
+
+#: rhodecode/lib/utils2.py:357
+#, python-format
+msgid "%s and %s ago"
+msgstr "Il y a %s et %s"
+
+#: rhodecode/lib/utils2.py:360
+msgid "just now"
+msgstr "à l’instant"
+
+#: rhodecode/lib/celerylib/tasks.py:269
+msgid "password reset link"
+msgstr "Réinitialisation du mot de passe"
+
+#: rhodecode/model/comment.py:110
+#, python-format
+msgid "on line %s"
+msgstr "à la ligne %s"
+
+#: rhodecode/model/comment.py:157
+msgid "[Mention]"
+msgstr "[Mention]"
+
+#: rhodecode/model/db.py:1140
+#, fuzzy
+msgid "Repository no access"
+msgstr "Dépôts"
+
+#: rhodecode/model/db.py:1141
+#, fuzzy
+msgid "Repository read access"
+msgstr "Ce dépôt existe déjà"
+
+#: rhodecode/model/db.py:1142
+#, fuzzy
+msgid "Repository write access"
+msgstr "Dépôts"
+
+#: rhodecode/model/db.py:1143
+#, fuzzy
+msgid "Repository admin access"
+msgstr "Dépôts"
+
+#: rhodecode/model/db.py:1145
+#, fuzzy
+msgid "Repositories Group no access"
+msgstr "Groupes de dépôts"
+
+#: rhodecode/model/db.py:1146
+#, fuzzy
+msgid "Repositories Group read access"
+msgstr "Groupes de dépôts"
+
+#: rhodecode/model/db.py:1147
+#, fuzzy
+msgid "Repositories Group write access"
+msgstr "Groupes de dépôts"
+
+#: rhodecode/model/db.py:1148
+#, fuzzy
+msgid "Repositories Group admin access"
+msgstr "Groupes de dépôts"
+
+#: rhodecode/model/db.py:1150
+#, fuzzy
+msgid "RhodeCode Administrator"
+msgstr "Administration des utilisateurs"
+
+#: rhodecode/model/db.py:1151
+#, fuzzy
+msgid "Repository creation disabled"
+msgstr "Création de dépôt"
+
+#: rhodecode/model/db.py:1152
+#, fuzzy
+msgid "Repository creation enabled"
+msgstr "Création de dépôt"
+
+#: rhodecode/model/db.py:1153
+#, fuzzy
+msgid "Repository forking disabled"
+msgstr "Création de dépôt"
+
+#: rhodecode/model/db.py:1154
+#, fuzzy
+msgid "Repository forking enabled"
+msgstr "Création de dépôt"
+
+#: rhodecode/model/db.py:1155
+#, fuzzy
+msgid "Register disabled"
+msgstr "Désactivé"
+
+#: rhodecode/model/db.py:1156
+msgid "Register new user with RhodeCode with manual activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1159
+msgid "Register new user with RhodeCode with auto activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1579
+msgid "Not Reviewed"
+msgstr ""
+
+#: rhodecode/model/db.py:1580
+#, fuzzy
+msgid "Approved"
+msgstr "Supprimés"
+
+#: rhodecode/model/db.py:1581
+msgid "Rejected"
+msgstr ""
+
+#: rhodecode/model/db.py:1582
+msgid "Under Review"
+msgstr ""
+
+#: rhodecode/model/forms.py:43
+msgid "Please enter a login"
+msgstr "Veuillez entrer un identifiant"
+
+#: rhodecode/model/forms.py:44
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr "Entrez une valeur d’au moins %(min)i caractères de long."
+
+#: rhodecode/model/forms.py:52
+msgid "Please enter a password"
+msgstr "Veuillez entrer un mot de passe"
+
+#: rhodecode/model/forms.py:53
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr "Entrez au moins %(min)i caractères"
+
+#: rhodecode/model/notification.py:220
+msgid "commented on commit"
+msgstr "a posté un commentaire sur le commit"
+
+#: rhodecode/model/notification.py:221
+msgid "sent message"
+msgstr "a envoyé un message"
+
+#: rhodecode/model/notification.py:222
+msgid "mentioned you"
+msgstr "vous a mentioné"
+
+#: rhodecode/model/notification.py:223
+msgid "registered in RhodeCode"
+msgstr "s’est enregistré sur RhodeCode"
+
+#: rhodecode/model/notification.py:224
+msgid "opened new pull request"
+msgstr ""
+
+#: rhodecode/model/notification.py:225
+#, fuzzy
+msgid "commented on pull request"
+msgstr "a posté un commentaire sur le commit"
+
+#: rhodecode/model/pull_request.py:84
+#, python-format
+msgid "%(user)s wants you to review pull request #%(pr_id)s"
+msgstr ""
+
+#: rhodecode/model/scm.py:535
+#, fuzzy
+msgid "latest tip"
+msgstr "Dernière connexion"
+
+#: rhodecode/model/user.py:230
+msgid "new user registration"
+msgstr "Nouveau compte utilisateur enregistré"
+
+#: rhodecode/model/user.py:255 rhodecode/model/user.py:277
+#: rhodecode/model/user.py:299
+msgid "You can't Edit this user since it's crucial for entire application"
+msgstr ""
+"Vous ne pouvez pas éditer cet utilisateur ; il est nécessaire pour le bon"
+" fonctionnement de l’application."
+
+#: rhodecode/model/user.py:323
+msgid "You can't remove this user since it's crucial for entire application"
+msgstr ""
+"Vous ne pouvez pas supprimer cet utilisateur ; il est nécessaire pour le "
+"bon fonctionnement de l’application."
+
+#: rhodecode/model/user.py:329
+#, python-format
+msgid ""
+"user \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories. %s"
+msgstr ""
+"L’utilisateur « %s » possède %s dépôts et ne peut être supprimé. Changez "
+"les propriétaires de ces dépôts. %s"
+
+#: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: rhodecode/model/validators.py:82
+#, fuzzy, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr "Ce nom \"%(username)s\" d’utilisateur existe déjà"
+
+#: rhodecode/model/validators.py:84
+#, python-format
+msgid "Username \"%(username)s\" is forbidden"
+msgstr ""
+
+#: rhodecode/model/validators.py:86
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or"
+" dashes and must begin with alphanumeric character"
+msgstr ""
+"Le nom d’utilisateur peut contenir uniquement des caractères alpha-"
+"numériques ainsi que les caractères suivants : « _ . - ». Il doit "
+"commencer par un caractère alpha-numérique."
+
+#: rhodecode/model/validators.py:114
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr "%(username)s Nom d'utilisateur n'est pas valide"
+
+#: rhodecode/model/validators.py:133
+#, fuzzy
+msgid "Invalid users group name"
+msgstr "nom d’utilisateur invalide"
+
+#: rhodecode/model/validators.py:134
+#, python-format
+msgid "Users group \"%(usersgroup)s\" already exists"
+msgstr "Ce groupe \"%(usersgroup)s\" d’utilisateurs existe déjà."
+
+#: rhodecode/model/validators.py:136
+msgid ""
+"users group name may only contain  alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+"Le nom de groupe de dépôts peut contenir uniquement des caractères alpha-"
+"numériques ainsi que les caractères suivants : « _ . - ». Il doit "
+"commencer par un caractère alpha-numérique."
+
+#: rhodecode/model/validators.py:174
+msgid "Cannot assign this group as parent"
+msgstr "Impossible d’assigner ce groupe en tant que parent."
+
+#: rhodecode/model/validators.py:175
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr "Ce nom d’utilisateur \"%(group_name)s\" existe déjà"
+
+#: rhodecode/model/validators.py:177
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr "Dépôt avec le nom de \"%(group_name)s\" existe déjà"
+
+#: rhodecode/model/validators.py:235
+#, fuzzy
+msgid "Invalid characters (non-ascii) in password"
+msgstr "Caractères incorrects dans le mot de passe"
+
+#: rhodecode/model/validators.py:250
+msgid "Passwords do not match"
+msgstr "Les mots de passe ne correspondent pas."
+
+#: rhodecode/model/validators.py:267
+msgid "invalid password"
+msgstr "mot de passe invalide"
+
+#: rhodecode/model/validators.py:268
+msgid "invalid user name"
+msgstr "nom d’utilisateur invalide"
+
+#: rhodecode/model/validators.py:269
+msgid "Your account is disabled"
+msgstr "Votre compte est désactivé"
+
+#: rhodecode/model/validators.py:313
+#, python-format
+msgid "Repository name %(repo)s is disallowed"
+msgstr "Ce nom de dépôt %(repo)s est interdit"
+
+#: rhodecode/model/validators.py:315
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr "Un dépôt portant %(repo)s ce nom existe déjà."
+
+#: rhodecode/model/validators.py:316
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr "Ce dépôt \"%(repo)s\" existe déjà dans le groupe « \"%(group)s\" »."
+
+#: rhodecode/model/validators.py:318
+#, python-format
+msgid "Repositories group with name \"%(repo)s\" already exists"
+msgstr "Un dépôt portant \"%(repo)s\" ce nom existe déjà."
+
+#: rhodecode/model/validators.py:431
+msgid "invalid clone url"
+msgstr "URL de clonage invalide."
+
+#: rhodecode/model/validators.py:432
+#, fuzzy
+msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
+msgstr ""
+"URL à cloner invalide. Veuillez fournir une URL valide commençant par "
+"http(s)."
+
+#: rhodecode/model/validators.py:457
+#, fuzzy
+msgid "Fork have to be the same type as parent"
+msgstr "Le fork doit être du même type que l’original"
+
+#: rhodecode/model/validators.py:478
+msgid "This username or users group name is not valid"
+msgstr "Ce nom d’utilisateur ou de groupe n’est pas valide."
+
+#: rhodecode/model/validators.py:562
+msgid "This is not a valid path"
+msgstr "Ceci n’est pas un chemin valide"
+
+#: rhodecode/model/validators.py:577
+msgid "This e-mail address is already taken"
+msgstr "Cette adresse e-mail est déjà enregistrée"
+
+#: rhodecode/model/validators.py:597
+#, python-format
+msgid "e-mail \"%(email)s\" does not exist."
+msgstr "Cette adresse e-mail \"%(email)s\" n’existe pas"
+
+#: rhodecode/model/validators.py:634
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+"L’attribut Login du CN doit être spécifié. Cet attribut correspond au nom"
+" d’utilisateur."
+
+#: rhodecode/model/validators.py:653
+#, python-format
+msgid "Revisions %(revs)s are already part of pull request or have set status"
+msgstr ""
+
+#: rhodecode/templates/index.html:3
+msgid "Dashboard"
+msgstr "Tableau de bord"
+
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/repo_switcher_list.html:4
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/users/user_edit_my_account.html:31
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/bookmarks/bookmarks.html:10
+#: rhodecode/templates/branches/branches.html:9
+#: rhodecode/templates/journal/journal.html:40
+#: rhodecode/templates/tags/tags.html:10
+msgid "quick filter..."
+msgstr "Filtre rapide…"
+
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/base/base.html:221
+msgid "repositories"
+msgstr "Dépôts"
+
+#: rhodecode/templates/index_base.html:13
+#: rhodecode/templates/index_base.html:15
+#: rhodecode/templates/admin/repos/repos.html:21
+msgid "ADD REPOSITORY"
+msgstr "AJOUTER UN DÉPÔT"
+
+#: rhodecode/templates/index_base.html:29
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
+#: rhodecode/templates/admin/users_groups/users_group_add.html:32
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:33
+msgid "Group name"
+msgstr "Nom de groupe"
+
+#: rhodecode/templates/index_base.html:30
+#: rhodecode/templates/index_base.html:71
+#: rhodecode/templates/index_base.html:142
+#: rhodecode/templates/index_base.html:168
+#: rhodecode/templates/admin/repos/repo_add_base.html:56
+#: rhodecode/templates/admin/repos/repo_edit.html:75
+#: rhodecode/templates/admin/repos/repos.html:72
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
+#: rhodecode/templates/forks/fork.html:59
+#: rhodecode/templates/settings/repo_settings.html:66
+#: rhodecode/templates/summary/summary.html:105
+msgid "Description"
+msgstr "Description"
+
+#: rhodecode/templates/index_base.html:40
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
+msgid "Repositories group"
+msgstr "Groupe de dépôts"
+
+#: rhodecode/templates/index_base.html:70
+#: rhodecode/templates/index_base.html:166
+#: rhodecode/templates/admin/repos/repo_add_base.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:32
+#: rhodecode/templates/admin/repos/repos.html:70
+#: rhodecode/templates/admin/users/user_edit.html:192
+#: rhodecode/templates/admin/users/user_edit_my_account.html:59
+#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
+#: rhodecode/templates/bookmarks/bookmarks.html:36
+#: rhodecode/templates/bookmarks/bookmarks_data.html:6
+#: rhodecode/templates/branches/branches.html:51
+#: rhodecode/templates/files/files_browser.html:47
+#: rhodecode/templates/journal/journal.html:59
+#: rhodecode/templates/journal/journal.html:107
+#: rhodecode/templates/journal/journal.html:186
+#: rhodecode/templates/settings/repo_settings.html:31
+#: rhodecode/templates/summary/summary.html:43
+#: rhodecode/templates/summary/summary.html:123
+#: rhodecode/templates/tags/tags.html:36
+#: rhodecode/templates/tags/tags_data.html:6
+msgid "Name"
+msgstr "Nom"
+
+#: rhodecode/templates/index_base.html:72
+msgid "Last change"
+msgstr "Dernière modification"
+
+#: rhodecode/templates/index_base.html:73
+#: rhodecode/templates/index_base.html:171
+#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/journal/journal.html:188
+msgid "Tip"
+msgstr "Sommet"
+
+#: rhodecode/templates/index_base.html:74
+#: rhodecode/templates/index_base.html:173
+#: rhodecode/templates/admin/repos/repo_edit.html:121
+#: rhodecode/templates/admin/repos/repos.html:73
+msgid "Owner"
+msgstr "Propriétaire"
+
+#: rhodecode/templates/index_base.html:75
+#: rhodecode/templates/summary/summary.html:48
+#: rhodecode/templates/summary/summary.html:51
+msgid "RSS"
+msgstr "RSS"
+
+#: rhodecode/templates/index_base.html:76
+msgid "Atom"
+msgstr "Atom"
+
+#: rhodecode/templates/index_base.html:110
+#: rhodecode/templates/index_base.html:112
+#, python-format
+msgid "Subscribe to %s rss feed"
+msgstr "S’abonner au flux RSS de %s"
+
+#: rhodecode/templates/index_base.html:117
+#: rhodecode/templates/index_base.html:119
+#, python-format
+msgid "Subscribe to %s atom feed"
+msgstr "S’abonner au flux ATOM de %s"
+
+#: rhodecode/templates/index_base.html:140
+msgid "Group Name"
+msgstr "Nom du groupe"
+
+#: rhodecode/templates/index_base.html:158
+#: rhodecode/templates/index_base.html:198
+#: rhodecode/templates/admin/repos/repos.html:94
+#: rhodecode/templates/admin/users/user_edit_my_account.html:179
+#: rhodecode/templates/admin/users/users.html:107
+#: rhodecode/templates/bookmarks/bookmarks.html:60
+#: rhodecode/templates/branches/branches.html:77
+#: rhodecode/templates/journal/journal.html:211
+#: rhodecode/templates/tags/tags.html:60
+msgid "Click to sort ascending"
+msgstr "Tri ascendant"
+
+#: rhodecode/templates/index_base.html:159
+#: rhodecode/templates/index_base.html:199
+#: rhodecode/templates/admin/repos/repos.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account.html:180
+#: rhodecode/templates/admin/users/users.html:108
+#: rhodecode/templates/bookmarks/bookmarks.html:61
+#: rhodecode/templates/branches/branches.html:78
+#: rhodecode/templates/journal/journal.html:212
+#: rhodecode/templates/tags/tags.html:61
+msgid "Click to sort descending"
+msgstr "Tri descendant"
+
+#: rhodecode/templates/index_base.html:169
+msgid "Last Change"
+msgstr "Dernière modification"
+
+#: rhodecode/templates/index_base.html:200
+#: rhodecode/templates/admin/repos/repos.html:96
+#: rhodecode/templates/admin/users/user_edit_my_account.html:181
+#: rhodecode/templates/admin/users/users.html:109
+#: rhodecode/templates/bookmarks/bookmarks.html:62
+#: rhodecode/templates/branches/branches.html:79
+#: rhodecode/templates/journal/journal.html:213
+#: rhodecode/templates/tags/tags.html:62
+msgid "No records found."
+msgstr "Aucun élément n’a été trouvé."
+
+#: rhodecode/templates/index_base.html:201
+#: rhodecode/templates/admin/repos/repos.html:97
+#: rhodecode/templates/admin/users/user_edit_my_account.html:182
+#: rhodecode/templates/admin/users/users.html:110
+#: rhodecode/templates/bookmarks/bookmarks.html:63
+#: rhodecode/templates/branches/branches.html:80
+#: rhodecode/templates/journal/journal.html:214
+#: rhodecode/templates/tags/tags.html:63
+msgid "Data error."
+msgstr "Erreur d’intégrité des données."
+
+#: rhodecode/templates/index_base.html:202
+#: rhodecode/templates/admin/repos/repos.html:98
+#: rhodecode/templates/admin/users/user_edit_my_account.html:183
+#: rhodecode/templates/admin/users/users.html:111
+#: rhodecode/templates/bookmarks/bookmarks.html:64
+#: rhodecode/templates/branches/branches.html:81
+#: rhodecode/templates/journal/journal.html:215
+#: rhodecode/templates/tags/tags.html:64
+msgid "Loading..."
+msgstr "Chargement…"
+
+#: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
+msgid "Sign In"
+msgstr "Connexion"
+
+#: rhodecode/templates/login.html:21
+msgid "Sign In to"
+msgstr "Connexion à"
+
+#: 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
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
+#: rhodecode/templates/base/base.html:83
+#: rhodecode/templates/summary/summary.html:122
+msgid "Username"
+msgstr "Nom d’utilisateur"
+
+#: 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
+msgid "Password"
+msgstr "Mot de passe"
+
+#: rhodecode/templates/login.html:50
+msgid "Remember me"
+msgstr "Se souvenir de moi"
+
+#: rhodecode/templates/login.html:60
+msgid "Forgot your password ?"
+msgstr "Mot de passe oublié ?"
+
+#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
+msgid "Don't have an account ?"
+msgstr "Vous n’avez pas de compte ?"
+
+#: rhodecode/templates/password_reset.html:5
+msgid "Reset your password"
+msgstr "Mot de passe oublié ?"
+
+#: rhodecode/templates/password_reset.html:11
+msgid "Reset your password to"
+msgstr "Réinitialiser votre mot de passe"
+
+#: rhodecode/templates/password_reset.html:21
+msgid "Email address"
+msgstr "Adresse e-mail"
+
+#: rhodecode/templates/password_reset.html:30
+msgid "Reset my password"
+msgstr "Réinitialiser mon mot de passe"
+
+#: rhodecode/templates/password_reset.html:31
+msgid "Password reset link will be send to matching email address"
+msgstr "Votre nouveau mot de passe sera envoyé à l’adresse correspondante."
+
+#: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
+msgid "Sign Up"
+msgstr "Inscription"
+
+#: rhodecode/templates/register.html:11
+msgid "Sign Up to"
+msgstr "Inscription à"
+
+#: rhodecode/templates/register.html:38
+msgid "Re-enter password"
+msgstr "Confirmation"
+
+#: rhodecode/templates/register.html:47
+#: rhodecode/templates/admin/users/user_add.html:59
+#: rhodecode/templates/admin/users/user_edit.html:86
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
+msgid "First Name"
+msgstr "Prénom"
+
+#: rhodecode/templates/register.html:56
+#: rhodecode/templates/admin/users/user_add.html:68
+#: rhodecode/templates/admin/users/user_edit.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
+msgid "Last Name"
+msgstr "Nom"
+
+#: rhodecode/templates/register.html:65
+#: rhodecode/templates/admin/users/user_add.html:77
+#: rhodecode/templates/admin/users/user_edit.html:104
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
+#: rhodecode/templates/summary/summary.html:124
+msgid "Email"
+msgstr "E-mail"
+
+#: rhodecode/templates/register.html:76
+msgid "Your account will be activated right after registration"
+msgstr "Votre compte utilisateur sera actif dès la fin de l’enregistrement."
+
+#: rhodecode/templates/register.html:78
+msgid "Your account must wait for activation by administrator"
+msgstr "Votre compte utilisateur devra être activé par un administrateur."
+
+#: rhodecode/templates/repo_switcher_list.html:11
+#: rhodecode/templates/admin/repos/repo_add_base.html:65
+#: rhodecode/templates/admin/repos/repo_edit.html:85
+#: rhodecode/templates/settings/repo_settings.html:76
+msgid "Private repository"
+msgstr "Dépôt privé"
+
+#: rhodecode/templates/repo_switcher_list.html:16
+msgid "Public repository"
+msgstr "Dépôt public"
+
+#: rhodecode/templates/switch_to_list.html:3
+#: rhodecode/templates/branches/branches.html:14
+msgid "branches"
+msgstr "Branches"
+
+#: rhodecode/templates/switch_to_list.html:10
+#: rhodecode/templates/branches/branches_data.html:57
+msgid "There are no branches yet"
+msgstr "Aucune branche n’a été créée pour le moment."
+
+#: rhodecode/templates/switch_to_list.html:15
+#: rhodecode/templates/shortlog/shortlog_data.html:10
+#: rhodecode/templates/tags/tags.html:15
+msgid "tags"
+msgstr "Tags"
+
+#: rhodecode/templates/switch_to_list.html:22
+#: rhodecode/templates/tags/tags_data.html:33
+msgid "There are no tags yet"
+msgstr "Aucun tag n’a été créé pour le moment."
+
+#: rhodecode/templates/switch_to_list.html:28
+#: rhodecode/templates/bookmarks/bookmarks.html:15
+msgid "bookmarks"
+msgstr "Signets"
+
+#: rhodecode/templates/switch_to_list.html:35
+#: rhodecode/templates/bookmarks/bookmarks_data.html:32
+msgid "There are no bookmarks yet"
+msgstr "Aucun signet n’a été créé."
+
+#: rhodecode/templates/admin/admin.html:5
+#: rhodecode/templates/admin/admin.html:9
+msgid "Admin journal"
+msgstr "Historique d’administration"
+
+#: rhodecode/templates/admin/admin_log.html:6
+#: rhodecode/templates/admin/repos/repos.html:74
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
+#: rhodecode/templates/journal/journal.html:61
+#: rhodecode/templates/journal/journal.html:62
+msgid "Action"
+msgstr "Action"
+
+#: rhodecode/templates/admin/admin_log.html:7
+msgid "Repository"
+msgstr "Dépôt"
+
+#: rhodecode/templates/admin/admin_log.html:8
+#: rhodecode/templates/bookmarks/bookmarks.html:37
+#: rhodecode/templates/bookmarks/bookmarks_data.html:7
+#: rhodecode/templates/branches/branches.html:52
+#: rhodecode/templates/tags/tags.html:37
+#: rhodecode/templates/tags/tags_data.html:7
+msgid "Date"
+msgstr "Date"
+
+#: rhodecode/templates/admin/admin_log.html:9
+msgid "From IP"
+msgstr "Depuis l’adresse IP"
+
+#: rhodecode/templates/admin/admin_log.html:53
+msgid "No actions yet"
+msgstr "Aucune action n’a été enregistrée pour le moment."
+
+#: rhodecode/templates/admin/ldap/ldap.html:5
+msgid "LDAP administration"
+msgstr "Administration LDAP"
+
+#: rhodecode/templates/admin/ldap/ldap.html:11
+msgid "Ldap"
+msgstr "LDAP"
+
+#: rhodecode/templates/admin/ldap/ldap.html:28
+msgid "Connection settings"
+msgstr "Options de connexion"
+
+#: rhodecode/templates/admin/ldap/ldap.html:30
+msgid "Enable LDAP"
+msgstr "Activer le LDAP"
+
+#: rhodecode/templates/admin/ldap/ldap.html:34
+msgid "Host"
+msgstr "Serveur"
+
+#: rhodecode/templates/admin/ldap/ldap.html:38
+msgid "Port"
+msgstr "Port"
+
+#: rhodecode/templates/admin/ldap/ldap.html:42
+msgid "Account"
+msgstr "Compte"
+
+#: rhodecode/templates/admin/ldap/ldap.html:50
+msgid "Connection security"
+msgstr "Connexion sécurisée"
+
+#: rhodecode/templates/admin/ldap/ldap.html:54
+msgid "Certificate Checks"
+msgstr "Vérif. des certificats"
+
+#: rhodecode/templates/admin/ldap/ldap.html:57
+msgid "Search settings"
+msgstr "Réglages de recherche"
+
+#: rhodecode/templates/admin/ldap/ldap.html:59
+msgid "Base DN"
+msgstr "Base de recherche"
+
+#: rhodecode/templates/admin/ldap/ldap.html:63
+msgid "LDAP Filter"
+msgstr "Filtre de recherche"
+
+#: rhodecode/templates/admin/ldap/ldap.html:67
+msgid "LDAP Search Scope"
+msgstr "Portée de recherche"
+
+#: rhodecode/templates/admin/ldap/ldap.html:70
+msgid "Attribute mappings"
+msgstr "Correspondance des attributs"
+
+#: rhodecode/templates/admin/ldap/ldap.html:72
+msgid "Login Attribute"
+msgstr "Attribut pour le nom d’utilisateur"
+
+#: rhodecode/templates/admin/ldap/ldap.html:76
+msgid "First Name Attribute"
+msgstr "Attribut pour le prénom"
+
+#: rhodecode/templates/admin/ldap/ldap.html:80
+msgid "Last Name Attribute"
+msgstr "Attribut pour le nom de famille"
+
+#: rhodecode/templates/admin/ldap/ldap.html:84
+msgid "E-mail Attribute"
+msgstr "Attribut pour l’e-mail"
+
+#: rhodecode/templates/admin/ldap/ldap.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:141
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
+#: rhodecode/templates/admin/settings/hooks.html:73
+#: rhodecode/templates/admin/users/user_edit.html:129
+#: rhodecode/templates/admin/users/user_edit.html:174
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:135
+#: rhodecode/templates/settings/repo_settings.html:93
+msgid "Save"
+msgstr "Enregistrer"
+
+#: rhodecode/templates/admin/notifications/notifications.html:5
+#: rhodecode/templates/admin/notifications/notifications.html:9
+msgid "My Notifications"
+msgstr "Mes notifications"
+
+#: rhodecode/templates/admin/notifications/notifications.html:29
+msgid "All"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:30
+#, fuzzy
+msgid "Comments"
+msgstr "commits"
+
+#: rhodecode/templates/admin/notifications/notifications.html:31
+#: rhodecode/templates/base/base.html:254
+#: rhodecode/templates/base/base.html:256
+msgid "Pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:35
+msgid "Mark all read"
+msgstr "Tout marquer comme lu"
+
+#: rhodecode/templates/admin/notifications/notifications_data.html:39
+msgid "No notifications here yet"
+msgstr "Aucune notification pour le moment."
+
+#: rhodecode/templates/admin/notifications/show_notification.html:5
+#: rhodecode/templates/admin/notifications/show_notification.html:11
+msgid "Show notification"
+msgstr "Notification"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:9
+msgid "Notifications"
+msgstr "Notifications"
+
+#: rhodecode/templates/admin/permissions/permissions.html:5
+msgid "Permissions administration"
+msgstr "Gestion des permissions"
+
+#: rhodecode/templates/admin/permissions/permissions.html:11
+#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
+#: rhodecode/templates/admin/users/user_edit.html:139
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:100
+#: rhodecode/templates/settings/repo_settings.html:86
+msgid "Permissions"
+msgstr "Permissions"
+
+#: rhodecode/templates/admin/permissions/permissions.html:24
+msgid "Default permissions"
+msgstr "Permissions par défaut"
+
+#: rhodecode/templates/admin/permissions/permissions.html:31
+msgid "Anonymous access"
+msgstr "Accès anonyme"
+
+#: rhodecode/templates/admin/permissions/permissions.html:41
+msgid "Repository permission"
+msgstr "Permissions du dépôt"
+
+#: 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 ""
+"Les permissions par défaut de chaque dépôt vont être remplacées par la "
+"permission choisie. Toutes les permissions par défaut des dépôts seront "
+"perdues."
+
+#: rhodecode/templates/admin/permissions/permissions.html:50
+msgid "overwrite existing settings"
+msgstr "Écraser les permissions existantes"
+
+#: rhodecode/templates/admin/permissions/permissions.html:55
+msgid "Registration"
+msgstr "Enregistrement"
+
+#: rhodecode/templates/admin/permissions/permissions.html:63
+msgid "Repository creation"
+msgstr "Création de dépôt"
+
+#: rhodecode/templates/admin/permissions/permissions.html:71
+#, fuzzy
+msgid "Repository forking"
+msgstr "Création de dépôt"
+
+#: rhodecode/templates/admin/permissions/permissions.html:78
+#: rhodecode/templates/admin/repos/repo_edit.html:241
+msgid "set"
+msgstr "Définir"
+
+#: rhodecode/templates/admin/repos/repo_add.html:5
+#: rhodecode/templates/admin/repos/repo_add_create_repository.html:5
+msgid "Add repository"
+msgstr "Ajouter un dépôt"
+
+#: rhodecode/templates/admin/repos/repo_add.html:11
+#: rhodecode/templates/admin/repos/repo_edit.html:11
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
+msgid "Repositories"
+msgstr "Dépôts"
+
+#: rhodecode/templates/admin/repos/repo_add.html:13
+msgid "add new"
+msgstr "ajouter un nouveau"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:20
+#: rhodecode/templates/summary/summary.html:95
+#: rhodecode/templates/summary/summary.html:96
+msgid "Clone from"
+msgstr "Cloner depuis"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:24
+#: rhodecode/templates/admin/repos/repo_edit.html:44
+#: rhodecode/templates/settings/repo_settings.html:43
+msgid "Optional http[s] url from which repository should be cloned."
+msgstr "URL http(s) depuis laquelle le dépôt doit être cloné."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:29
+#: rhodecode/templates/admin/repos/repo_edit.html:49
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:4
+#: rhodecode/templates/forks/fork.html:50
+#: rhodecode/templates/settings/repo_settings.html:48
+msgid "Repository group"
+msgstr "Groupe de dépôt"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:33
+#: rhodecode/templates/forks/fork.html:54
+#, fuzzy
+msgid "Optionaly select a group to put this repository into."
+msgstr "Sélectionnez un groupe (optionel) dans lequel sera placé le dépôt."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:38
+#: rhodecode/templates/admin/repos/repo_edit.html:58
+msgid "Type"
+msgstr "Type"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:42
+msgid "Type of repository to create."
+msgstr "Type de dépôt à créer."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:47
+#: rhodecode/templates/admin/repos/repo_edit.html:66
+#: rhodecode/templates/forks/fork.html:41
+#: rhodecode/templates/settings/repo_settings.html:57
+#, fuzzy
+msgid "Landing revision"
+msgstr "révision suivante"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:51
+#: rhodecode/templates/admin/repos/repo_edit.html:70
+#: rhodecode/templates/forks/fork.html:45
+#: rhodecode/templates/settings/repo_settings.html:61
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:60
+#: 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."
+msgstr ""
+"Gardez cette description précise et concise. Utilisez un fichier README "
+"pour des descriptions plus détaillées."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:69
+#: 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 ""
+"Les dépôts privés sont visibles seulement par les utilisateurs ajoutés "
+"comme collaborateurs."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:73
+msgid "add"
+msgstr "Ajouter"
+
+#: rhodecode/templates/admin/repos/repo_add_create_repository.html:9
+msgid "add new repository"
+msgstr "ajouter un nouveau dépôt"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:5
+msgid "Edit repository"
+msgstr "Éditer le dépôt"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:13
+#: rhodecode/templates/admin/users/user_edit.html:13
+#: rhodecode/templates/admin/users/user_edit.html:224
+#: rhodecode/templates/admin/users/user_edit.html:226
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:13
+#: rhodecode/templates/files/files_source.html:44
+#: rhodecode/templates/journal/journal.html:81
+msgid "edit"
+msgstr "éditer"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:40
+#: rhodecode/templates/settings/repo_settings.html:39
+msgid "Clone uri"
+msgstr "URL de clone"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:53
+#: rhodecode/templates/settings/repo_settings.html:52
+msgid "Optional select a group to put this repository into."
+msgstr "Sélectionnez un groupe (optionel) dans lequel sera placé le dépôt."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:94
+msgid "Enable statistics"
+msgstr "Activer les statistiques"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:98
+msgid "Enable statistics window on summary page."
+msgstr "Afficher les statistiques sur la page du dépôt."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:103
+msgid "Enable downloads"
+msgstr "Activer les téléchargements"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:107
+msgid "Enable download menu on summary page."
+msgstr "Afficher le menu de téléchargements sur la page du dépôt."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:112
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
+#, fuzzy
+msgid "Enable locking"
+msgstr "Activer"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:116
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:126
+msgid "Change owner of this repository."
+msgstr "Changer le propriétaire de ce dépôt."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:142
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
+#: rhodecode/templates/admin/settings/settings.html:113
+#: rhodecode/templates/admin/settings/settings.html:168
+#: rhodecode/templates/admin/settings/settings.html:258
+#: rhodecode/templates/admin/users/user_edit.html:130
+#: rhodecode/templates/admin/users/user_edit.html:175
+#: rhodecode/templates/admin/users/user_edit.html:278
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:136
+#: rhodecode/templates/files/files_add.html:82
+#: rhodecode/templates/files/files_edit.html:68
+#: rhodecode/templates/pullrequests/pullrequest.html:124
+#: rhodecode/templates/settings/repo_settings.html:94
+msgid "Reset"
+msgstr "Réinitialiser"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:152
+msgid "Administration"
+msgstr "Administration"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:155
+msgid "Statistics"
+msgstr "Statistiques"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:159
+msgid "Reset current statistics"
+msgstr "Réinitialiser les statistiques"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:159
+msgid "Confirm to remove current statistics"
+msgstr "Souhaitez-vous vraiment réinitialiser les statistiques de ce dépôt ?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:162
+msgid "Fetched to rev"
+msgstr "Parcouru jusqu’à la révision"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:163
+msgid "Stats gathered"
+msgstr "Statistiques obtenues"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:171
+msgid "Remote"
+msgstr "Dépôt distant"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Pull changes from remote location"
+msgstr "Récupérer les changements depuis le site distant"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Confirm to pull changes from remote side"
+msgstr "Voulez-vous vraiment récupérer les changements depuis le site distant ?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:186
+msgid "Cache"
+msgstr "Cache"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:190
+msgid "Invalidate repository cache"
+msgstr "Invalider le cache du dépôt"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:190
+msgid "Confirm to invalidate repository cache"
+msgstr "Voulez-vous vraiment invalider le cache du dépôt ?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:195
+#: rhodecode/templates/base/base.html:318
+#: rhodecode/templates/base/base.html:320
+#: rhodecode/templates/base/base.html:322
+msgid "Public journal"
+msgstr "Journal public"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:201
+msgid "Remove from public journal"
+msgstr "Supprimer du journal public"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:203
+msgid "Add to public journal"
+msgstr "Ajouter le dépôt au journal public"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:208
+msgid ""
+"All actions made on this repository will be accessible to everyone in "
+"public journal"
+msgstr ""
+"Le descriptif des actions réalisées sur ce dépôt sera visible à tous "
+"depuis le journal public."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:215
+#, fuzzy
+msgid "Locking"
+msgstr "Déverrouiller"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Unlock locked repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+#, fuzzy
+msgid "Confirm to unlock repository"
+msgstr "Voulez-vous vraiment supprimer ce dépôt ?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "lock repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+#, fuzzy
+msgid "Confirm to lock repository"
+msgstr "Voulez-vous vraiment supprimer ce dépôt ?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:224
+#, fuzzy
+msgid "Repository is not locked"
+msgstr "Dépôts"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:229
+msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:236
+#, fuzzy
+msgid "Set as fork of"
+msgstr "Indiquer comme fork"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:245
+#, fuzzy
+msgid "Manually set this repository as a fork of another from the list"
+msgstr "Permet d’indiquer manuellement que ce dépôt est un fork d’un autre dépôt."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:251
+#: rhodecode/templates/changeset/changeset_file_comment.html:26
+msgid "Delete"
+msgstr "Supprimer"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+msgid "Remove this repository"
+msgstr "Supprimer ce dépôt"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+#: rhodecode/templates/journal/journal.html:84
+msgid "Confirm to delete this repository"
+msgstr "Voulez-vous vraiment supprimer ce dépôt ?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:259
+msgid ""
+"This repository will be renamed in a special way in order to be "
+"unaccesible for RhodeCode and VCS systems.\n"
+"                         If you need fully delete it from filesystem "
+"please do it manually"
+msgstr ""
+"Ce dépôt sera renommé de manière à le rendre inaccessible à RhodeCode et "
+"au système de gestion de versions.\n"
+"Si vous voulez le supprimer complètement, effectuez la suppression "
+"manuellement."
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:3
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
+msgid "none"
+msgstr "Aucune"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:4
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
+msgid "read"
+msgstr "Lecture"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:5
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
+msgid "write"
+msgstr "Écriture"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:6
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
+#: rhodecode/templates/admin/users/users.html:85
+#: rhodecode/templates/base/base.html:217
+msgid "admin"
+msgstr "Administration"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:7
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
+msgid "member"
+msgstr "Membre"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
+#: rhodecode/templates/data_table/_dt_elements.html:67
+#: rhodecode/templates/journal/journal.html:132
+#: rhodecode/templates/summary/summary.html:76
+msgid "private repository"
+msgstr "Dépôt privé"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:19
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:28
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
+msgid "default"
+msgstr "[Par défaut]"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:33
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:58
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
+msgid "revoke"
+msgstr "Révoquer"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:83
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
+msgid "Add another member"
+msgstr "Ajouter un utilisateur"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:97
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
+msgid "Failed to remove user"
+msgstr "Échec de suppression de l’utilisateur"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:112
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
+msgid "Failed to remove users group"
+msgstr "Erreur lors de la suppression du groupe d’utilisateurs."
+
+#: rhodecode/templates/admin/repos/repos.html:5
+msgid "Repositories administration"
+msgstr "Administration des dépôts"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:8
+msgid "Groups"
+msgstr "Groupes"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:12
+msgid "with"
+msgstr "comprenant"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:5
+msgid "Add repos group"
+msgstr "Créer un groupe de dépôt"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:10
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:10
+msgid "Repos groups"
+msgstr "Groupes de dépôts"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:12
+msgid "add new repos group"
+msgstr "Nouveau groupe de dépôt"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:50
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:50
+msgid "Group parent"
+msgstr "Parent du groupe"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
+#: rhodecode/templates/admin/users/user_add.html:94
+#: rhodecode/templates/admin/users_groups/users_group_add.html:49
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:90
+#: rhodecode/templates/pullrequests/pullrequest_show.html:113
+msgid "save"
+msgstr "Enregistrer"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:5
+msgid "Edit repos group"
+msgstr "Éditer le groupe de dépôt"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:12
+msgid "edit repos group"
+msgstr "Édition du groupe de dépôt"
+
+#: 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 ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
+msgid "Repositories groups administration"
+msgstr "Administration des groupes de dépôts"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:22
+msgid "ADD NEW GROUP"
+msgstr "AJOUTER UN NOUVEAU GROUPE"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
+msgid "Number of toplevel repositories"
+msgstr "Nombre de sous-dépôts"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
+#: rhodecode/templates/admin/users/users.html:87
+#: rhodecode/templates/admin/users_groups/users_groups.html:35
+msgid "action"
+msgstr "Action"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#: rhodecode/templates/admin/users/user_edit.html:255
+#: rhodecode/templates/admin/users_groups/users_groups.html:44
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#: rhodecode/templates/data_table/_dt_elements.html:103
+msgid "delete"
+msgstr "Supprimer"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#, python-format
+msgid "Confirm to delete this group: %s"
+msgstr "Voulez-vous vraiment supprimer le groupe « %s » ?"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
+msgid "There are no repositories groups yet"
+msgstr "Aucun groupe de dépôts n’a été créé pour le moment."
+
+#: rhodecode/templates/admin/settings/hooks.html:5
+#: rhodecode/templates/admin/settings/settings.html:5
+msgid "Settings administration"
+msgstr "Administration générale"
+
+#: rhodecode/templates/admin/settings/hooks.html:9
+#: rhodecode/templates/admin/settings/settings.html:9
+#: rhodecode/templates/settings/repo_settings.html:13
+msgid "Settings"
+msgstr "Options"
+
+#: rhodecode/templates/admin/settings/hooks.html:24
+msgid "Built in hooks - read only"
+msgstr "Hooks prédéfinis (lecture seule)"
+
+#: rhodecode/templates/admin/settings/hooks.html:40
+msgid "Custom hooks"
+msgstr "Hooks personnalisés"
+
+#: rhodecode/templates/admin/settings/hooks.html:56
+msgid "remove"
+msgstr "Enlever"
+
+#: rhodecode/templates/admin/settings/hooks.html:88
+msgid "Failed to remove hook"
+msgstr "Erreur lors de la suppression du hook."
+
+#: rhodecode/templates/admin/settings/settings.html:24
+msgid "Remap and rescan repositories"
+msgstr "Ré-associer et re-scanner les dépôts"
+
+#: rhodecode/templates/admin/settings/settings.html:32
+msgid "rescan option"
+msgstr "Option de re-scan"
+
+#: 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 ""
+"Cochez cette option pour supprimer d’éventuelles données obsolètes "
+"(concernant des dépôts manuellement supprimés) de la base de données."
+
+#: rhodecode/templates/admin/settings/settings.html:39
+msgid "destroy old data"
+msgstr "Supprimer les données obsolètes"
+
+#: rhodecode/templates/admin/settings/settings.html:41
+msgid ""
+"Rescan repositories location for new repositories. Also deletes obsolete "
+"if `destroy` flag is checked "
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:46
+msgid "Rescan repositories"
+msgstr "Re-scanner les dépôts"
+
+#: rhodecode/templates/admin/settings/settings.html:52
+msgid "Whoosh indexing"
+msgstr "Indexation Whoosh"
+
+#: rhodecode/templates/admin/settings/settings.html:60
+msgid "index build option"
+msgstr "Option d’indexation"
+
+#: rhodecode/templates/admin/settings/settings.html:65
+msgid "build from scratch"
+msgstr "Purger et reconstruire l’index"
+
+#: rhodecode/templates/admin/settings/settings.html:71
+msgid "Reindex"
+msgstr "Mettre à jour l’index"
+
+#: rhodecode/templates/admin/settings/settings.html:77
+msgid "Global application settings"
+msgstr "Réglages d’application globaux"
+
+#: rhodecode/templates/admin/settings/settings.html:86
+msgid "Application name"
+msgstr "Nom de l’application"
+
+#: rhodecode/templates/admin/settings/settings.html:95
+msgid "Realm text"
+msgstr "Texte du royaume"
+
+#: rhodecode/templates/admin/settings/settings.html:104
+msgid "GA code"
+msgstr "Code GA"
+
+#: rhodecode/templates/admin/settings/settings.html:112
+#: rhodecode/templates/admin/settings/settings.html:167
+#: rhodecode/templates/admin/settings/settings.html:257
+msgid "Save settings"
+msgstr "Enregister les options"
+
+#: rhodecode/templates/admin/settings/settings.html:119
+#, fuzzy
+msgid "Visualisation settings"
+msgstr "Réglages d’application globaux"
+
+#: rhodecode/templates/admin/settings/settings.html:128
+#, fuzzy
+msgid "Icons"
+msgstr "Options"
+
+#: rhodecode/templates/admin/settings/settings.html:133
+msgid "Show public repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:137
+#, fuzzy
+msgid "Show private repo icon on repositories"
+msgstr "Dépôt privé"
+
+#: rhodecode/templates/admin/settings/settings.html:144
+#, fuzzy
+msgid "Meta-Tagging"
+msgstr "Réglages"
+
+#: rhodecode/templates/admin/settings/settings.html:149
+msgid "Stylify recognised metatags:"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:176
+#, fuzzy
+msgid "VCS settings"
+msgstr "Réglages"
+
+#: rhodecode/templates/admin/settings/settings.html:185
+msgid "Web"
+msgstr "Web"
+
+#: rhodecode/templates/admin/settings/settings.html:190
+#, fuzzy
+msgid "require ssl for vcs operations"
+msgstr "SSL requis pour les pushs"
+
+#: rhodecode/templates/admin/settings/settings.html:192
+msgid ""
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
+"will return HTTP Error 406: Not Acceptable"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:198
+msgid "Hooks"
+msgstr "Hooks"
+
+#: rhodecode/templates/admin/settings/settings.html:203
+msgid "Update repository after push (hg update)"
+msgstr "Mettre à jour les dépôts après un push (hg update)"
+
+#: rhodecode/templates/admin/settings/settings.html:207
+msgid "Show repository size after push"
+msgstr "Afficher la taille du dépôt après un push"
+
+#: rhodecode/templates/admin/settings/settings.html:211
+msgid "Log user push commands"
+msgstr "Journaliser les commandes de push"
+
+#: rhodecode/templates/admin/settings/settings.html:215
+msgid "Log user pull commands"
+msgstr "Journaliser les commandes de pull"
+
+#: rhodecode/templates/admin/settings/settings.html:219
+msgid "advanced setup"
+msgstr "Avancé"
+
+#: rhodecode/templates/admin/settings/settings.html:224
+#, fuzzy
+msgid "Mercurial Extensions"
+msgstr "Dépôt Mercurial"
+
+#: rhodecode/templates/admin/settings/settings.html:229
+msgid "largefiles extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:233
+msgid "hgsubversion extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:235
+msgid ""
+"Requires hgsubversion library installed. Allows clonning from svn remote "
+"locations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:245
+msgid "Repositories location"
+msgstr "Emplacement des dépôts"
+
+#: rhodecode/templates/admin/settings/settings.html:250
+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 ""
+"Ce réglage ne devrait pas être modifié en temps normal. Si vous devez "
+"vraiment le faire, redémarrer l’application une fois le changement "
+"effectué. Cliquez sur ce texte pour déverrouiller."
+
+#: rhodecode/templates/admin/settings/settings.html:251
+msgid "unlock"
+msgstr "Déverrouiller"
+
+#: rhodecode/templates/admin/settings/settings.html:252
+msgid ""
+"Location where repositories are stored. After changing this value a "
+"restart, and rescan is required"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:272
+msgid "Test Email"
+msgstr "E-mail de test"
+
+#: rhodecode/templates/admin/settings/settings.html:280
+msgid "Email to"
+msgstr "Envoyer l’e-mail à"
+
+#: rhodecode/templates/admin/settings/settings.html:288
+msgid "Send"
+msgstr "Envoyer"
+
+#: rhodecode/templates/admin/settings/settings.html:294
+msgid "System Info and Packages"
+msgstr "Information système et paquets"
+
+#: rhodecode/templates/admin/settings/settings.html:297
+msgid "show"
+msgstr "Montrer"
+
+#: rhodecode/templates/admin/users/user_add.html:5
+msgid "Add user"
+msgstr "Ajouter un utilisateur"
+
+#: rhodecode/templates/admin/users/user_add.html:10
+#: rhodecode/templates/admin/users/user_edit.html:11
+msgid "Users"
+msgstr "Utilisateurs"
+
+#: rhodecode/templates/admin/users/user_add.html:12
+msgid "add new user"
+msgstr "nouvel utilisateur"
+
+#: rhodecode/templates/admin/users/user_add.html:50
+msgid "Password confirmation"
+msgstr "Confirmation"
+
+#: rhodecode/templates/admin/users/user_add.html:86
+#: rhodecode/templates/admin/users/user_edit.html:113
+#: rhodecode/templates/admin/users_groups/users_group_add.html:41
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:42
+msgid "Active"
+msgstr "Actif"
+
+#: rhodecode/templates/admin/users/user_edit.html:5
+msgid "Edit user"
+msgstr "Éditer l'utilisateur"
+
+#: rhodecode/templates/admin/users/user_edit.html:34
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
+msgid "Change your avatar at"
+msgstr "Vous pouvez changer votre avatar sur"
+
+#: rhodecode/templates/admin/users/user_edit.html:35
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
+msgid "Using"
+msgstr "en utilisant l’adresse"
+
+#: rhodecode/templates/admin/users/user_edit.html:43
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
+msgid "API key"
+msgstr "Clé d’API"
+
+#: rhodecode/templates/admin/users/user_edit.html:59
+msgid "LDAP DN"
+msgstr "DN LDAP"
+
+#: rhodecode/templates/admin/users/user_edit.html:68
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
+msgid "New password"
+msgstr "Nouveau mot de passe"
+
+#: rhodecode/templates/admin/users/user_edit.html:77
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
+msgid "New password confirmation"
+msgstr "Confirmation du nouveau mot de passe"
+
+#: rhodecode/templates/admin/users/user_edit.html:147
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:108
+#, fuzzy
+msgid "Inherit default permissions"
+msgstr "Permissions par défaut"
+
+#: rhodecode/templates/admin/users/user_edit.html:152
+#: 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 ""
+
+#: rhodecode/templates/admin/users/user_edit.html:158
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
+msgid "Create repositories"
+msgstr "Création de dépôts"
+
+#: rhodecode/templates/admin/users/user_edit.html:166
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:127
+#, fuzzy
+msgid "Fork repositories"
+msgstr "Dépôts"
+
+#: rhodecode/templates/admin/users/user_edit.html:186
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
+#, fuzzy
+msgid "Nothing here yet"
+msgstr "Aucune notification pour le moment."
+
+#: rhodecode/templates/admin/users/user_edit.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account.html:60
+#: rhodecode/templates/admin/users/user_edit_my_account.html:194
+msgid "Permission"
+msgstr "Permission"
+
+#: rhodecode/templates/admin/users/user_edit.html:194
+#, fuzzy
+msgid "Edit Permission"
+msgstr "Permissions du dépôt"
+
+#: rhodecode/templates/admin/users/user_edit.html:243
+#, fuzzy
+msgid "Email addresses"
+msgstr "Adresse e-mail"
+
+#: rhodecode/templates/admin/users/user_edit.html:256
+#, fuzzy, python-format
+msgid "Confirm to delete this email: %s"
+msgstr "Voulez-vous vraiment supprimer l’utilisateur « %s » ?"
+
+#: rhodecode/templates/admin/users/user_edit.html:270
+#, fuzzy
+msgid "New email address"
+msgstr "Adresse e-mail"
+
+#: rhodecode/templates/admin/users/user_edit.html:277
+#, fuzzy
+msgid "Add"
+msgstr "Ajouter"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:5
+#: rhodecode/templates/base/base.html:124
+msgid "My account"
+msgstr "Mon compte"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:9
+msgid "My Account"
+msgstr "Mon compte"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:35
+msgid "My permissions"
+msgstr "Mes permissions"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:38
+#: rhodecode/templates/journal/journal.html:41
+msgid "My repos"
+msgstr "Mes dépôts"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:41
+#, fuzzy
+msgid "My pull requests"
+msgstr "a posté un commentaire sur le commit"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:45
+#, fuzzy
+msgid "Add repo"
+msgstr "ajouter un nouveau"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
+msgid "Opened by me"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
+#, python-format
+msgid "Pull request #%s opened on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
+#, fuzzy
+msgid "Confirm to delete this pull request"
+msgstr "Voulez-vous vraiment supprimer ce dépôt ?"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
+msgid "I participate in"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
+#, python-format
+msgid "Pull request #%s opened by %s on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
+#: rhodecode/templates/bookmarks/bookmarks.html:40
+#: rhodecode/templates/bookmarks/bookmarks_data.html:9
+#: rhodecode/templates/branches/branches.html:55
+#: rhodecode/templates/journal/journal.html:60
+#: rhodecode/templates/tags/tags.html:40
+#: rhodecode/templates/tags/tags_data.html:9
+msgid "Revision"
+msgstr "Révision"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/journal/journal.html:81
+msgid "private"
+msgstr "privé"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr "Voulez-vous vraiment supprimer le dépôt %s ?"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
+#: rhodecode/templates/journal/journal.html:94
+msgid "No repositories yet"
+msgstr "Aucun dépôt pour le moment"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
+#: rhodecode/templates/journal/journal.html:96
+msgid "create one now"
+msgstr "En créer un maintenant"
+
+#: rhodecode/templates/admin/users/users.html:5
+msgid "Users administration"
+msgstr "Administration des utilisateurs"
+
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/base/base.html:223
+msgid "users"
+msgstr "Utilisateurs"
+
+#: rhodecode/templates/admin/users/users.html:23
+msgid "ADD NEW USER"
+msgstr "NOUVEL UTILISATEUR"
+
+#: rhodecode/templates/admin/users/users.html:77
+msgid "username"
+msgstr "Nom d’utilisateur"
+
+#: rhodecode/templates/admin/users/users.html:80
+#, fuzzy
+msgid "firstname"
+msgstr "Prénom"
+
+#: rhodecode/templates/admin/users/users.html:81
+msgid "lastname"
+msgstr "Nom de famille"
+
+#: rhodecode/templates/admin/users/users.html:82
+msgid "last login"
+msgstr "Dernière connexion"
+
+#: rhodecode/templates/admin/users/users.html:84
+#: rhodecode/templates/admin/users_groups/users_groups.html:34
+msgid "active"
+msgstr "Actif"
+
+#: rhodecode/templates/admin/users/users.html:86
+#: rhodecode/templates/base/base.html:226
+msgid "ldap"
+msgstr "LDAP"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:5
+msgid "Add users group"
+msgstr "Ajouter un groupe d’utilisateur"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:10
+#: rhodecode/templates/admin/users_groups/users_groups.html:9
+msgid "Users groups"
+msgstr "Groupes d’utilisateurs"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:12
+msgid "add new users group"
+msgstr "Ajouter un nouveau groupe"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:5
+msgid "Edit users group"
+msgstr "Éditer le groupe d’utilisateurs"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:11
+msgid "UsersGroups"
+msgstr "UsersGroups"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:50
+msgid "Members"
+msgstr "Membres"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:58
+msgid "Choosen group members"
+msgstr "Membres du groupe"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:61
+msgid "Remove all elements"
+msgstr "Tout enlever"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:75
+msgid "Available members"
+msgstr "Membres disponibles"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:79
+msgid "Add all elements"
+msgstr "Tout ajouter"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:146
+msgid "Group members"
+msgstr "Membres du groupe"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:5
+msgid "Users groups administration"
+msgstr "Gestion des groupes d’utilisateurs"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:23
+msgid "ADD NEW USER GROUP"
+msgstr "AJOUTER UN NOUVEAU GROUPE"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:32
+msgid "group name"
+msgstr "Nom du groupe"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:33
+#: rhodecode/templates/base/root.html:46
+msgid "members"
+msgstr "Membres"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:45
+#, python-format
+msgid "Confirm to delete this users group: %s"
+msgstr "Voulez-vous vraiment supprimer le groupe d‘utilisateurs « %s » ?"
+
+#: rhodecode/templates/base/base.html:41
+msgid "Submit a bug"
+msgstr "Signaler un bogue"
+
+#: rhodecode/templates/base/base.html:77
+msgid "Login to your account"
+msgstr "Connexion à votre compte"
+
+#: rhodecode/templates/base/base.html:100
+msgid "Forgot password ?"
+msgstr "Mot de passe oublié ?"
+
+#: rhodecode/templates/base/base.html:107
+msgid "Log In"
+msgstr "Connexion"
+
+#: rhodecode/templates/base/base.html:118
+msgid "Inbox"
+msgstr "Boîte de réception"
+
+#: rhodecode/templates/base/base.html:122
+#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/base/base.html:302
+#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/bookmarks/bookmarks.html:11
+#: rhodecode/templates/branches/branches.html:10
+#: rhodecode/templates/changelog/changelog.html:10
+#: rhodecode/templates/changeset/changeset.html:10
+#: rhodecode/templates/changeset/changeset_range.html:9
+#: rhodecode/templates/compare/compare_diff.html:9
+#: rhodecode/templates/files/file_diff.html:8
+#: rhodecode/templates/files/files.html:8
+#: 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/pullrequests/pullrequest.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
+#: rhodecode/templates/settings/repo_settings.html:9
+#: rhodecode/templates/shortlog/shortlog.html:10
+#: rhodecode/templates/summary/summary.html:8
+#: rhodecode/templates/tags/tags.html:11
+msgid "Home"
+msgstr "Accueil"
+
+#: rhodecode/templates/base/base.html:123
+#: rhodecode/templates/base/base.html:309
+#: rhodecode/templates/base/base.html:311
+#: rhodecode/templates/base/base.html:313
+#: rhodecode/templates/journal/journal.html:4
+#: rhodecode/templates/journal/journal.html:21
+#: rhodecode/templates/journal/public_journal.html:4
+msgid "Journal"
+msgstr "Historique"
+
+#: rhodecode/templates/base/base.html:125
+msgid "Log Out"
+msgstr "Se déconnecter"
+
+#: rhodecode/templates/base/base.html:144
+msgid "Switch repository"
+msgstr "Aller au dépôt"
+
+#: rhodecode/templates/base/base.html:146
+msgid "Products"
+msgstr "Produits"
+
+#: rhodecode/templates/base/base.html:152
+#: rhodecode/templates/base/base.html:182
+msgid "loading..."
+msgstr "Chargement…"
+
+#: rhodecode/templates/base/base.html:158
+#: rhodecode/templates/base/base.html:160
+#: rhodecode/templates/base/base.html:162
+#: rhodecode/templates/data_table/_dt_elements.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:17
+#: rhodecode/templates/data_table/_dt_elements.html:19
+msgid "Summary"
+msgstr "Résumé"
+
+#: rhodecode/templates/base/base.html:166
+#: rhodecode/templates/base/base.html:168
+#: rhodecode/templates/base/base.html:170
+#: rhodecode/templates/changelog/changelog.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:23
+#: rhodecode/templates/data_table/_dt_elements.html:25
+#: rhodecode/templates/data_table/_dt_elements.html:27
+msgid "Changelog"
+msgstr "Historique"
+
+#: rhodecode/templates/base/base.html:175
+#: rhodecode/templates/base/base.html:177
+#: rhodecode/templates/base/base.html:179
+msgid "Switch to"
+msgstr "Aller"
+
+#: rhodecode/templates/base/base.html:186
+#: rhodecode/templates/base/base.html:188
+#: rhodecode/templates/base/base.html:190
+#: rhodecode/templates/data_table/_dt_elements.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:33
+#: rhodecode/templates/data_table/_dt_elements.html:35
+msgid "Files"
+msgstr "Fichiers"
+
+#: rhodecode/templates/base/base.html:195
+#: rhodecode/templates/base/base.html:199
+msgid "Options"
+msgstr "Options"
+
+#: rhodecode/templates/base/base.html:204
+#: rhodecode/templates/base/base.html:206
+#: rhodecode/templates/base/base.html:227
+msgid "settings"
+msgstr "Réglages"
+
+#: rhodecode/templates/base/base.html:209
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/forks/fork.html:13
+msgid "fork"
+msgstr "Fork"
+
+#: rhodecode/templates/base/base.html:211
+#: rhodecode/templates/changelog/changelog.html:40
+msgid "Open new pull request"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:213
+msgid "search"
+msgstr "Rechercher"
+
+#: rhodecode/templates/base/base.html:222
+msgid "repositories groups"
+msgstr "Groupes de dépôts"
+
+#: rhodecode/templates/base/base.html:224
+msgid "users groups"
+msgstr "Groupes d’utilisateurs"
+
+#: rhodecode/templates/base/base.html:225
+msgid "permissions"
+msgstr "Permissions"
+
+#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:240
+msgid "Followers"
+msgstr "Followers"
+
+#: rhodecode/templates/base/base.html:246
+#: rhodecode/templates/base/base.html:248
+msgid "Forks"
+msgstr "Forks"
+
+#: rhodecode/templates/base/base.html:327
+#: rhodecode/templates/base/base.html:329
+#: rhodecode/templates/base/base.html:331
+#: rhodecode/templates/search/search.html:52
+msgid "Search"
+msgstr "Rechercher"
+
+#: rhodecode/templates/base/root.html:42
+msgid "add another comment"
+msgstr "Nouveau commentaire"
+
+#: rhodecode/templates/base/root.html:43
+#: rhodecode/templates/journal/journal.html:120
+#: rhodecode/templates/summary/summary.html:57
+msgid "Stop following this repository"
+msgstr "Arrêter de suivre ce dépôt"
+
+#: rhodecode/templates/base/root.html:44
+#: rhodecode/templates/summary/summary.html:61
+msgid "Start following this repository"
+msgstr "Suivre ce dépôt"
+
+#: rhodecode/templates/base/root.html:45
+msgid "Group"
+msgstr "Groupe"
+
+#: rhodecode/templates/base/root.html:47
+msgid "search truncated"
+msgstr "Résultats tronqués"
+
+#: rhodecode/templates/base/root.html:48
+msgid "no matching files"
+msgstr "Aucun fichier ne correspond"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:5
+#, python-format
+msgid "%s Bookmarks"
+msgstr "Signets de %s"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:39
+#: rhodecode/templates/bookmarks/bookmarks_data.html:8
+#: rhodecode/templates/branches/branches.html:54
+#: rhodecode/templates/tags/tags.html:39
+#: rhodecode/templates/tags/tags_data.html:8
+msgid "Author"
+msgstr "Auteur"
+
+#: rhodecode/templates/branches/branches.html:5
+#, python-format
+msgid "%s Branches"
+msgstr "Branches de %s"
+
+#: rhodecode/templates/branches/branches.html:29
+#, fuzzy
+msgid "Compare branches"
+msgstr "Branches"
+
+#: rhodecode/templates/branches/branches.html:57
+#: rhodecode/templates/compare/compare_diff.html:5
+#: rhodecode/templates/compare/compare_diff.html:13
+#, fuzzy
+msgid "Compare"
+msgstr "vue de comparaison"
+
+#: rhodecode/templates/branches/branches_data.html:6
+msgid "name"
+msgstr "Prénom"
+
+#: rhodecode/templates/branches/branches_data.html:7
+msgid "date"
+msgstr "Date"
+
+#: rhodecode/templates/branches/branches_data.html:8
+#: rhodecode/templates/shortlog/shortlog_data.html:8
+msgid "author"
+msgstr "Auteur"
+
+#: rhodecode/templates/branches/branches_data.html:9
+#: rhodecode/templates/shortlog/shortlog_data.html:5
+msgid "revision"
+msgstr "Révision"
+
+#: rhodecode/templates/branches/branches_data.html:10
+#, fuzzy
+msgid "compare"
+msgstr "vue de comparaison"
+
+#: rhodecode/templates/changelog/changelog.html:6
+#, python-format
+msgid "%s Changelog"
+msgstr "Historique de %s"
+
+#: rhodecode/templates/changelog/changelog.html:15
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] "Affichage de %d révision sur %d"
+msgstr[1] "Affichage de %d révisions sur %d"
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:19
+#, python-format
+msgid "compare fork with %s"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:21
+#, fuzzy
+msgid "Compare fork"
+msgstr "vue de comparaison"
+
+#: rhodecode/templates/changelog/changelog.html:46
+msgid "Show"
+msgstr "Afficher"
+
+#: rhodecode/templates/changelog/changelog.html:72
+#: rhodecode/templates/summary/summary.html:364
+msgid "show more"
+msgstr "montrer plus"
+
+#: rhodecode/templates/changelog/changelog.html:76
+msgid "Affected number of files, click to show more details"
+msgstr "Nombre de fichiers modifiés, cliquez pour plus de détails"
+
+#: rhodecode/templates/changelog/changelog.html:89
+#: rhodecode/templates/changeset/changeset.html:38
+#: rhodecode/templates/changeset/changeset_file_comment.html:20
+#: rhodecode/templates/changeset/changeset_range.html:46
+#, fuzzy
+msgid "Changeset status"
+msgstr "Changesets"
+
+#: rhodecode/templates/changelog/changelog.html:92
+msgid "Click to open associated pull request"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:102
+#: rhodecode/templates/changeset/changeset.html:78
+msgid "Parent"
+msgstr "Parent"
+
+#: rhodecode/templates/changelog/changelog.html:108
+#: rhodecode/templates/changeset/changeset.html:84
+msgid "No parents"
+msgstr "Aucun parent"
+
+#: rhodecode/templates/changelog/changelog.html:113
+#: rhodecode/templates/changeset/changeset.html:88
+msgid "merge"
+msgstr "Fusion"
+
+#: rhodecode/templates/changelog/changelog.html:116
+#: rhodecode/templates/changeset/changeset.html:91
+#: rhodecode/templates/files/files.html:29
+#: rhodecode/templates/files/files_add.html:33
+#: rhodecode/templates/files/files_edit.html:33
+#: rhodecode/templates/shortlog/shortlog_data.html:9
+msgid "branch"
+msgstr "Branche"
+
+#: rhodecode/templates/changelog/changelog.html:122
+msgid "bookmark"
+msgstr "Signet"
+
+#: rhodecode/templates/changelog/changelog.html:128
+#: rhodecode/templates/changeset/changeset.html:96
+msgid "tag"
+msgstr "Tag"
+
+#: rhodecode/templates/changelog/changelog.html:164
+msgid "Show selected changes __S -> __E"
+msgstr "Afficher les changements sélections de __S à __E"
+
+#: rhodecode/templates/changelog/changelog.html:255
+msgid "There are no changes yet"
+msgstr "Il n’y a aucun changement pour le moment"
+
+#: rhodecode/templates/changelog/changelog_details.html:4
+#: rhodecode/templates/changeset/changeset.html:66
+msgid "removed"
+msgstr "Supprimés"
+
+#: rhodecode/templates/changelog/changelog_details.html:5
+#: rhodecode/templates/changeset/changeset.html:67
+msgid "changed"
+msgstr "Modifiés"
+
+#: rhodecode/templates/changelog/changelog_details.html:6
+#: rhodecode/templates/changeset/changeset.html:68
+msgid "added"
+msgstr "Ajoutés"
+
+#: rhodecode/templates/changelog/changelog_details.html:8
+#: rhodecode/templates/changelog/changelog_details.html:9
+#: rhodecode/templates/changelog/changelog_details.html:10
+#: rhodecode/templates/changeset/changeset.html:70
+#: rhodecode/templates/changeset/changeset.html:71
+#: rhodecode/templates/changeset/changeset.html:72
+#, python-format
+msgid "affected %s files"
+msgstr "%s fichiers affectés"
+
+#: rhodecode/templates/changeset/changeset.html:6
+#, python-format
+msgid "%s Changeset"
+msgstr "Changeset de %s"
+
+#: rhodecode/templates/changeset/changeset.html:14
+msgid "Changeset"
+msgstr "Changements"
+
+#: rhodecode/templates/changeset/changeset.html:43
+#: rhodecode/templates/changeset/diff_block.html:20
+msgid "raw diff"
+msgstr "Diff brut"
+
+#: rhodecode/templates/changeset/changeset.html:44
+#: rhodecode/templates/changeset/diff_block.html:21
+msgid "download diff"
+msgstr "Télécharger le diff"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d commentaire"
+msgstr[1] "%d commentaires"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "(%d inline)"
+msgid_plural "(%d inline)"
+msgstr[0] "(et %d en ligne)"
+msgstr[1] "(et %d en ligne)"
+
+#: rhodecode/templates/changeset/changeset.html:103
+#, python-format
+msgid "%s files affected with %s insertions and %s deletions:"
+msgstr "%s fichiers affectés avec %s insertions et %s suppressions :"
+
+#: rhodecode/templates/changeset/changeset.html:119
+msgid "Changeset was too big and was cut off..."
+msgstr "Cet ensemble de changements était trop important et a été découpé…"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:42
+msgid "Submitting..."
+msgstr "Envoi…"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:45
+msgid "Commenting on line {1}."
+msgstr "Commentaire sur la ligne {1}."
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:46
+#: rhodecode/templates/changeset/changeset_file_comment.html:121
+#, python-format
+msgid "Comments parsed using %s syntax with %s support."
+msgstr ""
+"Les commentaires sont analysés avec la syntaxe %s, avec le support de la "
+"commande %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"
+msgstr ""
+"Utilisez @nomutilisateur dans ce texte pour envoyer une notification à "
+"l’utilisateur RhodeCode en question."
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:59
+#: rhodecode/templates/changeset/changeset_file_comment.html:138
+msgid "Comment"
+msgstr "Commentaire"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:60
+#: rhodecode/templates/changeset/changeset_file_comment.html:71
+msgid "Hide"
+msgstr "Masquer"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "You need to be logged in to comment."
+msgstr "Vous devez être connecté pour poster des commentaires."
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "Login now"
+msgstr "Se connecter maintenant"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:118
+msgid "Leave a comment"
+msgstr "Laisser un commentaire"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "Check this to change current status of code-review for this changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+#, fuzzy
+msgid "change status"
+msgstr "Changesets"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:140
+msgid "Comment and close"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr "Changesets de %s"
+
+#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/templates/compare/compare_diff.html:29
+msgid "Compare View"
+msgstr "Comparaison"
+
+#: rhodecode/templates/changeset/changeset_range.html:54
+#: rhodecode/templates/compare/compare_diff.html:41
+#: rhodecode/templates/pullrequests/pullrequest_show.html:69
+msgid "Files affected"
+msgstr "Fichiers affectés"
+
+#: rhodecode/templates/changeset/diff_block.html:19
+msgid "diff"
+msgstr "Diff"
+
+#: rhodecode/templates/changeset/diff_block.html:27
+msgid "show inline comments"
+msgstr "Afficher les commentaires"
+
+#: rhodecode/templates/compare/compare_cs.html:5
+#, fuzzy
+msgid "No changesets"
+msgstr "Dépôt vide"
+
+#: rhodecode/templates/compare/compare_diff.html:37
+#, fuzzy
+msgid "Outgoing changesets"
+msgstr "Dépôt vide"
+
+#: rhodecode/templates/data_table/_dt_elements.html:39
+#: rhodecode/templates/data_table/_dt_elements.html:41
+#: rhodecode/templates/data_table/_dt_elements.html:43
+msgid "Fork"
+msgstr "Fork"
+
+#: rhodecode/templates/data_table/_dt_elements.html:60
+#: rhodecode/templates/journal/journal.html:126
+#: rhodecode/templates/summary/summary.html:68
+msgid "Mercurial repository"
+msgstr "Dépôt Mercurial"
+
+#: rhodecode/templates/data_table/_dt_elements.html:62
+#: rhodecode/templates/journal/journal.html:128
+#: rhodecode/templates/summary/summary.html:71
+msgid "Git repository"
+msgstr "Dépôt Git"
+
+#: rhodecode/templates/data_table/_dt_elements.html:69
+#: rhodecode/templates/journal/journal.html:134
+#: rhodecode/templates/summary/summary.html:78
+msgid "public repository"
+msgstr "Dépôt public"
+
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/summary/summary.html:87
+#: rhodecode/templates/summary/summary.html:88
+msgid "Fork of"
+msgstr "Fork de"
+
+#: rhodecode/templates/data_table/_dt_elements.html:92
+msgid "No changesets yet"
+msgstr "Dépôt vide"
+
+#: rhodecode/templates/data_table/_dt_elements.html:104
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr "Voulez-vous vraiment supprimer l’utilisateur « %s » ?"
+
+#: rhodecode/templates/email_templates/main.html:8
+msgid "This is an notification from RhodeCode."
+msgstr "Ceci est une notification de RhodeCode."
+
+#: rhodecode/templates/errors/error_document.html:46
+#, python-format
+msgid "You will be redirected to %s in %s seconds"
+msgstr "Vous serez redirigé vers %s dans %s secondes."
+
+#: rhodecode/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File diff"
+msgstr "Diff de fichier de %s"
+
+#: rhodecode/templates/files/file_diff.html:12
+msgid "File diff"
+msgstr "Diff de fichier"
+
+#: rhodecode/templates/files/files.html:4
+#: rhodecode/templates/files/files.html:72
+#, fuzzy, python-format
+msgid "%s files"
+msgstr "Fichiers de %s"
+
+#: rhodecode/templates/files/files.html:12
+#: rhodecode/templates/summary/summary.html:340
+msgid "files"
+msgstr "Fichiers"
+
+#: rhodecode/templates/files/files_add.html:4
+#: rhodecode/templates/files/files_edit.html:4
+#, python-format
+msgid "%s Edit file"
+msgstr "Edition de fichier de %s"
+
+#: rhodecode/templates/files/files_add.html:19
+msgid "add file"
+msgstr "Ajouter un fichier"
+
+#: rhodecode/templates/files/files_add.html:40
+msgid "Add new file"
+msgstr "Ajouter un nouveau fichier"
+
+#: rhodecode/templates/files/files_add.html:45
+msgid "File Name"
+msgstr "Nom de fichier"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:58
+msgid "or"
+msgstr "ou"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:54
+msgid "Upload file"
+msgstr "Téléverser un fichier"
+
+#: rhodecode/templates/files/files_add.html:58
+msgid "Create new file"
+msgstr "Créer un nouveau fichier"
+
+#: rhodecode/templates/files/files_add.html:63
+#: rhodecode/templates/files/files_edit.html:39
+#: rhodecode/templates/files/files_ypjax.html:3
+msgid "Location"
+msgstr "Emplacement"
+
+#: rhodecode/templates/files/files_add.html:67
+msgid "use / to separate directories"
+msgstr "Utilisez / pour séparer les répertoires"
+
+#: rhodecode/templates/files/files_add.html:77
+#: rhodecode/templates/files/files_edit.html:63
+#: rhodecode/templates/shortlog/shortlog_data.html:6
+msgid "commit message"
+msgstr "Message de commit"
+
+#: rhodecode/templates/files/files_add.html:81
+#: rhodecode/templates/files/files_edit.html:67
+msgid "Commit changes"
+msgstr "Commiter les changements"
+
+#: rhodecode/templates/files/files_browser.html:13
+msgid "view"
+msgstr "voir"
+
+#: rhodecode/templates/files/files_browser.html:14
+msgid "previous revision"
+msgstr "révision précédente"
+
+#: rhodecode/templates/files/files_browser.html:16
+msgid "next revision"
+msgstr "révision suivante"
+
+#: rhodecode/templates/files/files_browser.html:23
+msgid "follow current branch"
+msgstr "Suivre la branche actuelle"
+
+#: rhodecode/templates/files/files_browser.html:27
+msgid "search file list"
+msgstr "Rechercher un fichier"
+
+#: rhodecode/templates/files/files_browser.html:31
+#: rhodecode/templates/shortlog/shortlog_data.html:65
+msgid "add new file"
+msgstr "Ajouter un fichier"
+
+#: rhodecode/templates/files/files_browser.html:35
+msgid "Loading file list..."
+msgstr "Chargement de la liste des fichiers…"
+
+#: rhodecode/templates/files/files_browser.html:48
+msgid "Size"
+msgstr "Taille"
+
+#: rhodecode/templates/files/files_browser.html:49
+msgid "Mimetype"
+msgstr "Type MIME"
+
+#: rhodecode/templates/files/files_browser.html:50
+msgid "Last Revision"
+msgstr "Dernière révision"
+
+#: rhodecode/templates/files/files_browser.html:51
+msgid "Last modified"
+msgstr "Dernière modification"
+
+#: rhodecode/templates/files/files_browser.html:52
+msgid "Last commiter"
+msgstr "Dernier commiteur"
+
+#: rhodecode/templates/files/files_edit.html:19
+msgid "edit file"
+msgstr "Éditer le fichier"
+
+#: rhodecode/templates/files/files_edit.html:49
+#: rhodecode/templates/files/files_source.html:38
+msgid "show annotation"
+msgstr "Afficher les annotations"
+
+#: rhodecode/templates/files/files_edit.html:50
+#: rhodecode/templates/files/files_source.html:40
+#: rhodecode/templates/files/files_source.html:68
+msgid "show as raw"
+msgstr "montrer le fichier brut"
+
+#: rhodecode/templates/files/files_edit.html:51
+#: rhodecode/templates/files/files_source.html:41
+msgid "download as raw"
+msgstr "télécharger le fichier brut"
+
+#: rhodecode/templates/files/files_edit.html:54
+msgid "source"
+msgstr "Source"
+
+#: rhodecode/templates/files/files_edit.html:59
+msgid "Editing file"
+msgstr "Édition du fichier"
+
+#: rhodecode/templates/files/files_source.html:2
+msgid "History"
+msgstr "Historique"
+
+#: rhodecode/templates/files/files_source.html:9
+#, fuzzy
+msgid "diff to revision"
+msgstr "révision suivante"
+
+#: rhodecode/templates/files/files_source.html:10
+#, fuzzy
+msgid "show at revision"
+msgstr "révision suivante"
+
+#: rhodecode/templates/files/files_source.html:14
+#, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] "%s Auteur"
+msgstr[1] "%s Auteurs"
+
+#: rhodecode/templates/files/files_source.html:36
+msgid "show source"
+msgstr "montrer les sources"
+
+#: rhodecode/templates/files/files_source.html:59
+#, python-format
+msgid "Binary file (%s)"
+msgstr "Fichier binaire (%s)"
+
+#: rhodecode/templates/files/files_source.html:68
+msgid "File is too big to display"
+msgstr "Ce fichier est trop gros pour être affiché."
+
+#: rhodecode/templates/files/files_source.html:124
+msgid "Selection link"
+msgstr "Lien vers la sélection"
+
+#: rhodecode/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr "annotation"
+
+#: rhodecode/templates/files/files_ypjax.html:15
+msgid "Go back"
+msgstr "Revenir en arrière"
+
+#: rhodecode/templates/files/files_ypjax.html:16
+msgid "No files at given path"
+msgstr "Aucun fichier à cet endroit"
+
+#: rhodecode/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr "Followers de %s"
+
+#: rhodecode/templates/followers/followers.html:13
+msgid "followers"
+msgstr "followers"
+
+#: rhodecode/templates/followers/followers_data.html:12
+msgid "Started following -"
+msgstr "A commencé à suivre le dépôt :"
+
+#: rhodecode/templates/forks/fork.html:5
+#, python-format
+msgid "%s Fork"
+msgstr "Fork de %s"
+
+#: rhodecode/templates/forks/fork.html:31
+msgid "Fork name"
+msgstr "Nom du fork"
+
+#: rhodecode/templates/forks/fork.html:68
+msgid "Private"
+msgstr "Privé"
+
+#: rhodecode/templates/forks/fork.html:77
+msgid "Copy permissions"
+msgstr "Copier les permissions"
+
+#: rhodecode/templates/forks/fork.html:81
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:86
+msgid "Update after clone"
+msgstr "MÀJ après le clonage"
+
+#: rhodecode/templates/forks/fork.html:90
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:94
+msgid "fork this repository"
+msgstr "Forker ce dépôt"
+
+#: rhodecode/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
+msgstr "Forks de %s"
+
+#: rhodecode/templates/forks/forks.html:13
+msgid "forks"
+msgstr "forks"
+
+#: rhodecode/templates/forks/forks_data.html:17
+msgid "forked"
+msgstr "forké"
+
+#: rhodecode/templates/forks/forks_data.html:38
+msgid "There are no forks yet"
+msgstr "Il n’y a pas encore de forks."
+
+#: rhodecode/templates/journal/journal.html:13
+#, fuzzy
+msgid "ATOM journal feed"
+msgstr "%s — Flux %s du journal public"
+
+#: rhodecode/templates/journal/journal.html:14
+#, fuzzy
+msgid "RSS journal feed"
+msgstr "%s — Flux %s du journal public"
+
+#: rhodecode/templates/journal/journal.html:24
+#: rhodecode/templates/pullrequests/pullrequest.html:27
+msgid "Refresh"
+msgstr "Rafraîchir"
+
+#: rhodecode/templates/journal/journal.html:27
+#: rhodecode/templates/journal/public_journal.html:24
+#, fuzzy
+msgid "RSS feed"
+msgstr "Flux %s de %s"
+
+#: rhodecode/templates/journal/journal.html:30
+#: rhodecode/templates/journal/public_journal.html:27
+msgid "ATOM feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:41
+msgid "Watched"
+msgstr "Surveillé"
+
+#: rhodecode/templates/journal/journal.html:46
+msgid "ADD"
+msgstr "AJOUTER"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "following user"
+msgstr "utilisateur suivant"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "user"
+msgstr "utilisateur"
+
+#: rhodecode/templates/journal/journal.html:147
+msgid "You are not following any users or repositories"
+msgstr "Vous ne suivez aucun utilisateur ou dépôt"
+
+#: rhodecode/templates/journal/journal_data.html:47
+msgid "No entries yet"
+msgstr "Aucune entrée pour le moment"
+
+#: rhodecode/templates/journal/public_journal.html:13
+#, fuzzy
+msgid "ATOM public journal feed"
+msgstr "%s — Flux %s du journal public"
+
+#: rhodecode/templates/journal/public_journal.html:14
+#, fuzzy
+msgid "RSS public journal feed"
+msgstr "%s — Flux %s du journal public"
+
+#: rhodecode/templates/journal/public_journal.html:21
+msgid "Public Journal"
+msgstr "Journal public"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:4
+#: rhodecode/templates/pullrequests/pullrequest.html:12
+msgid "New pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:28
+msgid "refresh overview"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:66
+#, fuzzy
+msgid "Detailed compare view"
+msgstr "vue de comparaison"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:70
+#: rhodecode/templates/pullrequests/pullrequest_show.html:82
+msgid "Pull request reviewers"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:79
+#: rhodecode/templates/pullrequests/pullrequest_show.html:94
+#, fuzzy
+msgid "owner"
+msgstr "Propriétaire"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:91
+#: rhodecode/templates/pullrequests/pullrequest_show.html:109
+msgid "Add reviewer to this pull request."
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:97
+#, fuzzy
+msgid "Create new pull request"
+msgstr "Créer un nouveau fichier"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:106
+#: rhodecode/templates/pullrequests/pullrequest_show.html:25
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
+#, fuzzy
+msgid "Title"
+msgstr "Écriture"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:115
+#, fuzzy
+msgid "description"
+msgstr "Description"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:123
+msgid "Send pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:23
+#, python-format
+msgid "Closed %s"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:31
+#, fuzzy
+msgid "Status"
+msgstr "Changesets"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:36
+msgid "Pull request status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:44
+msgid "Still not reviewed by"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:47
+#, python-format
+msgid "%d reviewer"
+msgid_plural "%d reviewers"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:54
+#, fuzzy
+msgid "Created on"
+msgstr "En créer un maintenant"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:61
+#, fuzzy
+msgid "Compare view"
+msgstr "vue de comparaison"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:65
+#, fuzzy
+msgid "Incoming changesets"
+msgstr "Dépôt vide"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
+#, fuzzy
+msgid "all pull requests"
+msgstr "Créer un nouveau fichier"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
+msgid "All pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
+msgid "Closed"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:6
+#, python-format
+msgid "Search \"%s\" in repository: %s"
+msgstr "dans \"%s\" le dépôt: %s"
+
+#: rhodecode/templates/search/search.html:8
+#, python-format
+msgid "Search \"%s\" in all repositories"
+msgstr "Recherche \"%s\" dans tous les référentiels"
+
+#: rhodecode/templates/search/search.html:12
+#: rhodecode/templates/search/search.html:32
+#, python-format
+msgid "Search in repository: %s"
+msgstr "dans le dépôt : %s"
+
+#: rhodecode/templates/search/search.html:14
+#: rhodecode/templates/search/search.html:34
+#, fuzzy
+msgid "Search in all repositories"
+msgstr "dans tous les dépôts"
+
+#: rhodecode/templates/search/search.html:48
+msgid "Search term"
+msgstr "Termes de la recherches"
+
+#: rhodecode/templates/search/search.html:60
+msgid "Search in"
+msgstr "Rechercher dans"
+
+#: rhodecode/templates/search/search.html:63
+msgid "File contents"
+msgstr "Le contenu des fichiers"
+
+#: rhodecode/templates/search/search.html:64
+#, fuzzy
+msgid "Commit messages"
+msgstr "Message de commit"
+
+#: rhodecode/templates/search/search.html:65
+msgid "File names"
+msgstr "Les noms de fichiers"
+
+#: rhodecode/templates/search/search_commit.html:35
+#: rhodecode/templates/search/search_content.html:21
+#: rhodecode/templates/search/search_path.html:15
+msgid "Permission denied"
+msgstr "Permission refusée"
+
+#: rhodecode/templates/settings/repo_settings.html:5
+#, python-format
+msgid "%s Settings"
+msgstr "Réglages de %s"
+
+#: rhodecode/templates/shortlog/shortlog.html:5
+#, python-format
+msgid "%s Shortlog"
+msgstr "Résumé de %s"
+
+#: rhodecode/templates/shortlog/shortlog.html:14
+msgid "shortlog"
+msgstr "Résumé"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:7
+msgid "age"
+msgstr "Âge"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:18
+msgid "No commit message"
+msgstr "Pas de message de commit"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:62
+msgid "Add or upload files directly via RhodeCode"
+msgstr "Ajouter ou téléverser des fichiers directement via RhodeCode…"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:71
+msgid "Push new repo"
+msgstr "Pusher le nouveau dépôt"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:79
+msgid "Existing repository?"
+msgstr "Le dépôt existe déjà ?"
+
+#: rhodecode/templates/summary/summary.html:4
+#, python-format
+msgid "%s Summary"
+msgstr "Résumé de %s"
+
+#: rhodecode/templates/summary/summary.html:12
+msgid "summary"
+msgstr "résumé"
+
+#: rhodecode/templates/summary/summary.html:20
+#, fuzzy, python-format
+msgid "repo %s ATOM feed"
+msgstr "S’abonner au flux ATOM de %s"
+
+#: rhodecode/templates/summary/summary.html:21
+#, fuzzy, python-format
+msgid "repo %s RSS feed"
+msgstr "S’abonner au flux RSS de %s"
+
+#: rhodecode/templates/summary/summary.html:49
+#: rhodecode/templates/summary/summary.html:52
+msgid "ATOM"
+msgstr "ATOM"
+
+#: rhodecode/templates/summary/summary.html:82
+#, python-format
+msgid "Non changable ID %s"
+msgstr "Identifiant permanent : %s"
+
+#: rhodecode/templates/summary/summary.html:87
+msgid "public"
+msgstr "publique"
+
+#: rhodecode/templates/summary/summary.html:95
+msgid "remote clone"
+msgstr "Clone distant"
+
+#: rhodecode/templates/summary/summary.html:116
+msgid "Contact"
+msgstr "Contact"
+
+#: rhodecode/templates/summary/summary.html:130
+msgid "Clone url"
+msgstr "URL de clone"
+
+#: rhodecode/templates/summary/summary.html:133
+msgid "Show by Name"
+msgstr "Afficher par nom"
+
+#: rhodecode/templates/summary/summary.html:134
+msgid "Show by ID"
+msgstr "Afficher par ID"
+
+#: rhodecode/templates/summary/summary.html:142
+msgid "Trending files"
+msgstr "Populaires"
+
+#: rhodecode/templates/summary/summary.html:150
+#: rhodecode/templates/summary/summary.html:166
+#: rhodecode/templates/summary/summary.html:194
+msgid "enable"
+msgstr "Activer"
+
+#: rhodecode/templates/summary/summary.html:158
+msgid "Download"
+msgstr "Téléchargements"
+
+#: rhodecode/templates/summary/summary.html:162
+msgid "There are no downloads yet"
+msgstr "Il n’y a pas encore de téléchargements proposés."
+
+#: rhodecode/templates/summary/summary.html:164
+msgid "Downloads are disabled for this repository"
+msgstr "Les téléchargements sont désactivés pour ce dépôt."
+
+#: rhodecode/templates/summary/summary.html:170
+msgid "Download as zip"
+msgstr "Télécharger en ZIP"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "Check this to download archive with subrepos"
+msgstr "Télécharger une archive contenant également les sous-dépôts éventuels"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "with subrepos"
+msgstr "avec les sous-dépôts"
+
+#: rhodecode/templates/summary/summary.html:186
+msgid "Commit activity by day / author"
+msgstr "Activité de commit par jour et par auteur"
+
+#: rhodecode/templates/summary/summary.html:197
+msgid "Stats gathered: "
+msgstr "Statistiques obtenues :"
+
+#: rhodecode/templates/summary/summary.html:218
+msgid "Shortlog"
+msgstr "Résumé des changements"
+
+#: rhodecode/templates/summary/summary.html:220
+msgid "Quick start"
+msgstr "Démarrage rapide"
+
+#: rhodecode/templates/summary/summary.html:233
+#, python-format
+msgid "Readme file at revision '%s'"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:236
+msgid "Permalink to this readme"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:293
+#, python-format
+msgid "Download %s as %s"
+msgstr "Télécharger %s comme archive %s"
+
+#: rhodecode/templates/summary/summary.html:650
+msgid "commits"
+msgstr "commits"
+
+#: rhodecode/templates/summary/summary.html:651
+msgid "files added"
+msgstr "fichiers ajoutés"
+
+#: rhodecode/templates/summary/summary.html:652
+msgid "files changed"
+msgstr "fichiers modifiés"
+
+#: rhodecode/templates/summary/summary.html:653
+msgid "files removed"
+msgstr "fichiers supprimés"
+
+#: rhodecode/templates/summary/summary.html:656
+msgid "commit"
+msgstr "commit"
+
+#: rhodecode/templates/summary/summary.html:657
+msgid "file added"
+msgstr "fichier ajouté"
+
+#: rhodecode/templates/summary/summary.html:658
+msgid "file changed"
+msgstr "fichié modifié"
+
+#: rhodecode/templates/summary/summary.html:659
+msgid "file removed"
+msgstr "fichier supprimé"
+
+#: rhodecode/templates/tags/tags.html:5
+#, python-format
+msgid "%s Tags"
+msgstr "Tags de %s"
+
diff --git a/rhodecode/i18n/how_to b/rhodecode/i18n/how_to
--- a/rhodecode/i18n/how_to
+++ b/rhodecode/i18n/how_to
@@ -4,25 +4,27 @@
 
 #this needs to be done on source codes, preferable default/stable branches
  
-python setup.py extract_messages -> get messages from project
-python setup.py init_catalog -l pl -> create a language directory for  lang
-edit the new po file with poedit or any other editor
-python setup.py compile_catalog -> create translation files
+python setup.py extract_messages <- get messages from project
+python setup.py init_catalog -l pl <- create a language directory for  lang
+#edit the new po file with poedit or any other editor
+msgfmt -f -c  <- check format and errors
+python setup.py compile_catalog <- create translation files
 
 ############# 
 # to update #
 #############
 
-python setup.py extract_messages -> get messages from project
-python setup.py update_catalog -> to update the translations
-edit the new updated po file with poedit
-python setup.py compile_catalog -> create translation files
+python setup.py extract_messages <- get messages from project
+python setup.py update_catalog <- to update the translations
+#edit the new updated po file with poedit
+msgfmt -f -c  <- check format and errors
+python setup.py compile_catalog <- create translation files
 
 
 ###################
 # change language #
 ###################
 
-`lang=en`
+`lang=pl`
 
 in the .ini file
\ No newline at end of file
diff --git a/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.mo b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.mo
new file mode 100644
index 0000000000000000000000000000000000000000..75399ccda196e369894ade4a9a4aefe0260b6c98
GIT binary patch
literal 53308
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

diff --git a/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po
new file mode 100644
--- /dev/null
+++ b/rhodecode/i18n/ja/LC_MESSAGES/rhodecode.po
@@ -0,0 +1,3929 @@
+# Japanese translations for RhodeCode.
+# Copyright (C) 2011 ORGANIZATION
+# This file is distributed under the same license as the RhodeCode project.
+# FIRST AUTHOR , 2011.
+# Takumi IINO  2012
+# WAKAYAMA Shirou  2012
+#
+# Mercurial Japanese Translation.
+# - http://selenic.com/repo/hg/file/stable/i18n/ja.po
+# ========================================
+msgid ""
+msgstr ""
+"Project-Id-Version: RhodeCode 1.2.0\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2012-09-02 20:30+0200\n"
+"PO-Revision-Date: 2012-07-14 03:16+0900\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: ja \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"
+
+#: rhodecode/controllers/changelog.py:94
+msgid "All Branches"
+msgstr "すべてのブランチ"
+
+#: rhodecode/controllers/changeset.py:83
+msgid "show white space"
+msgstr "空白を表示"
+
+#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+msgid "ignore white space"
+msgstr "空白を無視"
+
+#: rhodecode/controllers/changeset.py:157
+#, python-format
+msgid "%s line context"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:333
+#: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
+msgid "binary file"
+msgstr "バイナリファイル"
+
+#: rhodecode/controllers/changeset.py:408
+msgid ""
+"Changing status on a changeset associated witha closed pull request is "
+"not allowed"
+msgstr ""
+
+#: rhodecode/controllers/compare.py:69
+#, fuzzy
+msgid "There are no changesets yet"
+msgstr "まだ変更がありません"
+
+#: rhodecode/controllers/error.py:69
+msgid "Home page"
+msgstr "ホームページ"
+
+#: rhodecode/controllers/error.py:98
+msgid "The request could not be understood by the server due to malformed syntax."
+msgstr "形式が間違っているため、サーバーはリクエストを処理出来ませんでした"
+
+#: rhodecode/controllers/error.py:101
+msgid "Unauthorized access to resource"
+msgstr "リソースにアクセスする権限がありません"
+
+#: rhodecode/controllers/error.py:103
+msgid "You don't have permission to view this page"
+msgstr "このページを見る権限がありません"
+
+#: rhodecode/controllers/error.py:105
+msgid "The resource could not be found"
+msgstr "リソースが見つかりません"
+
+#: rhodecode/controllers/error.py:107
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+
+#: rhodecode/controllers/feed.py:49
+#, python-format
+msgid "Changes on %s repository"
+msgstr "%s リポジトリでの変更"
+
+#: rhodecode/controllers/feed.py:50
+#, python-format
+msgid "%s %s feed"
+msgstr "%s %s フィード"
+
+#: rhodecode/controllers/feed.py:75
+msgid "commited on"
+msgstr "コミット"
+
+#: rhodecode/controllers/files.py:84
+#, fuzzy
+msgid "click here to add new file"
+msgstr "新しいファイルを追加"
+
+#: rhodecode/controllers/files.py:85
+#, python-format
+msgid "There are no files yet %s"
+msgstr "まだファイルがありません %s"
+
+#: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
+#, python-format
+msgid "This repository is has been locked by %s on %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:266
+#, python-format
+msgid "Edited %s via RhodeCode"
+msgstr "RhodeCode経由で %s を変更"
+
+#: rhodecode/controllers/files.py:271
+msgid "No changes"
+msgstr "変更点なし"
+
+#: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
+#, python-format
+msgid "Successfully committed to %s"
+msgstr "%s へのコミットが成功しました"
+
+#: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
+msgid "Error occurred during commit"
+msgstr "コミット中にエラーが発生しました"
+
+#: rhodecode/controllers/files.py:318
+#, python-format
+msgid "Added %s via RhodeCode"
+msgstr "RhodeCode経由で %s を追加"
+
+#: rhodecode/controllers/files.py:332
+msgid "No content"
+msgstr "内容がありません"
+
+#: rhodecode/controllers/files.py:336
+msgid "No filename"
+msgstr "ファイル名がありません"
+
+#: rhodecode/controllers/files.py:378
+msgid "downloads disabled"
+msgstr "ダウンロードは無効化されています"
+
+#: rhodecode/controllers/files.py:389
+#, python-format
+msgid "Unknown revision %s"
+msgstr "%s は未知のリビジョンです"
+
+#: rhodecode/controllers/files.py:391
+msgid "Empty repository"
+msgstr "空のリポジトリ"
+
+#: rhodecode/controllers/files.py:393
+msgid "Unknown archive type"
+msgstr "未知のアーカイブ種別です"
+
+#: rhodecode/controllers/files.py:494
+#: rhodecode/templates/changeset/changeset_range.html:13
+#: rhodecode/templates/changeset/changeset_range.html:31
+msgid "Changesets"
+msgstr "チェンジセット"
+
+#: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
+#: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
+msgid "Branches"
+msgstr "ブランチ"
+
+#: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
+#: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
+msgid "Tags"
+msgstr "タグ"
+
+#: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
+#, 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 "
+"リポジトリはDB内に見つかりませんでした。おそらくファイルシステム上で作られたか名前が変更されたためです。リポジトリをもう一度チェックするためにアプリケーションを立ち上げ直してください。"
+
+#: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
+#, python-format
+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 "
+"リポジトリはDB内に見つかりませんでした。おそらくファイルシステム上で作られたか名前が変更されたためです。リポジトリをもう一度チェックするためにアプリケーションを立ち上げ直してください。"
+
+#: rhodecode/controllers/forks.py:167
+#, python-format
+msgid "forked %s repository as %s"
+msgstr "リポジトリ %s を %s としてフォーク"
+
+#: rhodecode/controllers/forks.py:181
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr "リポジトリ %s のフォーク中にエラーが発生しました"
+
+#: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
+msgid "public journal"
+msgstr "公開ジャーナル"
+
+#: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
+#: rhodecode/templates/base/base.html:220
+msgid "journal"
+msgstr "ジャーナル"
+
+#: rhodecode/controllers/login.py:143
+msgid "You have successfully registered into rhodecode"
+msgstr "rhodecodeへの登録を受け付けました"
+
+#: rhodecode/controllers/login.py:164
+msgid "Your password reset link was sent"
+msgstr "パスワードリセットのリンクを送信しました"
+
+#: rhodecode/controllers/login.py:184
+msgid ""
+"Your password reset was successful, new password has been sent to your "
+"email"
+msgstr "パスワードをリセットしました。新しいパスワードをあなたのメールアドレスに送りました"
+
+#: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
+msgid "Bookmarks"
+msgstr "ブックマーク"
+
+#: rhodecode/controllers/pullrequests.py:158
+msgid "Pull request requires a title with min. 3 chars"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:160
+#, fuzzy
+msgid "error during creation of pull request"
+msgstr "ユーザー %s の作成中にエラーが発生しました"
+
+#: rhodecode/controllers/pullrequests.py:181
+msgid "Successfully opened new pull request"
+msgstr "新しいプルリクエストを作成しました"
+
+#: rhodecode/controllers/pullrequests.py:184
+msgid "Error occurred during sending pull request"
+msgstr "プルリクエストの作成中にエラーが発生しました"
+
+#: rhodecode/controllers/pullrequests.py:217
+#, fuzzy
+msgid "Successfully deleted pull request"
+msgstr "新しいプルリクエストを作成しました"
+
+#: rhodecode/controllers/search.py:131
+msgid "Invalid search query. Try quoting it."
+msgstr "無効な検索クエリーです。\\\"で囲んで下さい"
+
+#: rhodecode/controllers/search.py:136
+msgid "There is no index to search in. Please run whoosh indexer"
+msgstr "検索するためのインデックスがありません。whooshでインデックスを作成して下さい"
+
+#: rhodecode/controllers/search.py:140
+msgid "An error occurred during this search operation"
+msgstr "検索を実行する際にエラーがおきました"
+
+#: rhodecode/controllers/settings.py:107
+#: rhodecode/controllers/admin/repos.py:266
+#, python-format
+msgid "Repository %s updated successfully"
+msgstr "リポジトリ %s の更新に成功しました"
+
+#: rhodecode/controllers/settings.py:125
+#: rhodecode/controllers/admin/repos.py:284
+#, python-format
+msgid "error occurred during update of repository %s"
+msgstr "リポジトリ %s の更新中にエラーが発生しました"
+
+#: rhodecode/controllers/settings.py:143
+#: rhodecode/controllers/admin/repos.py:302
+#, python-format
+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 "
+"リポジトリはDB内に見つかりませんでした。おそらくファイルシステム上で作られたか名前が変更されたためです。リポジトリをもう一度チェックするためにアプリケーションを立ち上げ直してください。"
+
+#: rhodecode/controllers/settings.py:155
+#: rhodecode/controllers/admin/repos.py:314
+#, python-format
+msgid "deleted repository %s"
+msgstr "リポジトリ %s を削除しました"
+
+#: rhodecode/controllers/settings.py:159
+#: rhodecode/controllers/admin/repos.py:324
+#: rhodecode/controllers/admin/repos.py:330
+#, python-format
+msgid "An error occurred during deletion of %s"
+msgstr "リポジトリ %s の削除中にエラーが発生しました"
+
+#: rhodecode/controllers/summary.py:138
+msgid "No data loaded yet"
+msgstr ""
+
+#: rhodecode/controllers/summary.py:142
+#: rhodecode/templates/summary/summary.html:148
+msgid "Statistics are disabled for this repository"
+msgstr "このリポジトリの統計は無効化されています"
+
+#: rhodecode/controllers/admin/ldap_settings.py:50
+msgid "BASE"
+msgstr "BASE"
+
+#: rhodecode/controllers/admin/ldap_settings.py:51
+msgid "ONELEVEL"
+msgstr "ONELEVEL"
+
+#: rhodecode/controllers/admin/ldap_settings.py:52
+msgid "SUBTREE"
+msgstr "SUBTREE"
+
+#: rhodecode/controllers/admin/ldap_settings.py:56
+msgid "NEVER"
+msgstr "NEVER"
+
+#: rhodecode/controllers/admin/ldap_settings.py:57
+msgid "ALLOW"
+msgstr "ALLOW"
+
+#: rhodecode/controllers/admin/ldap_settings.py:58
+msgid "TRY"
+msgstr "TRY"
+
+#: rhodecode/controllers/admin/ldap_settings.py:59
+msgid "DEMAND"
+msgstr "DEMAND"
+
+#: rhodecode/controllers/admin/ldap_settings.py:60
+msgid "HARD"
+msgstr "HARD"
+
+#: rhodecode/controllers/admin/ldap_settings.py:64
+msgid "No encryption"
+msgstr "暗号化なし"
+
+#: rhodecode/controllers/admin/ldap_settings.py:65
+msgid "LDAPS connection"
+msgstr "LDAPS接続"
+
+#: rhodecode/controllers/admin/ldap_settings.py:66
+msgid "START_TLS on LDAP connection"
+msgstr "LDAP接続でSTART_TLSを使用"
+
+#: rhodecode/controllers/admin/ldap_settings.py:126
+msgid "Ldap settings updated successfully"
+msgstr "LDAP設定を更新しました"
+
+#: rhodecode/controllers/admin/ldap_settings.py:130
+msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
+msgstr "LDAPを有効にできませんでした。\"python-ldap\"ライブラリがありません。"
+
+#: rhodecode/controllers/admin/ldap_settings.py:147
+msgid "error occurred during update of ldap settings"
+msgstr "LDAP設定の更新中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/permissions.py:59
+msgid "None"
+msgstr "なし"
+
+#: rhodecode/controllers/admin/permissions.py:60
+msgid "Read"
+msgstr "読込"
+
+#: rhodecode/controllers/admin/permissions.py:61
+msgid "Write"
+msgstr "書込"
+
+#: rhodecode/controllers/admin/permissions.py:62
+#: rhodecode/templates/admin/ldap/ldap.html:9
+#: rhodecode/templates/admin/permissions/permissions.html:9
+#: rhodecode/templates/admin/repos/repo_add.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:9
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
+#: rhodecode/templates/admin/settings/hooks.html:9
+#: rhodecode/templates/admin/settings/settings.html:9
+#: rhodecode/templates/admin/users/user_add.html:8
+#: rhodecode/templates/admin/users/user_edit.html:9
+#: rhodecode/templates/admin/users/user_edit.html:122
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/admin/users_groups/users_group_add.html:8
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:9
+#: rhodecode/templates/admin/users_groups/users_groups.html:9
+#: rhodecode/templates/base/base.html:197
+#: rhodecode/templates/base/base.html:337
+#: rhodecode/templates/base/base.html:339
+#: rhodecode/templates/base/base.html:341
+msgid "Admin"
+msgstr "管理"
+
+#: rhodecode/controllers/admin/permissions.py:65
+msgid "disabled"
+msgstr "無効にする"
+
+#: rhodecode/controllers/admin/permissions.py:67
+msgid "allowed with manual account activation"
+msgstr "手動でアカウントを有効にする"
+
+#: rhodecode/controllers/admin/permissions.py:69
+msgid "allowed with automatic account activation"
+msgstr "自動でアカウントを有効にする"
+
+#: rhodecode/controllers/admin/permissions.py:71
+#: rhodecode/controllers/admin/permissions.py:74
+msgid "Disabled"
+msgstr "無効"
+
+#: rhodecode/controllers/admin/permissions.py:72
+#: rhodecode/controllers/admin/permissions.py:75
+msgid "Enabled"
+msgstr "有効"
+
+#: rhodecode/controllers/admin/permissions.py:116
+msgid "Default permissions updated successfully"
+msgstr "デフォルトの権限を更新しました"
+
+#: rhodecode/controllers/admin/permissions.py:130
+msgid "error occurred during update of permissions"
+msgstr "権限の更新中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:123
+msgid "--REMOVE FORK--"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:192
+#, python-format
+msgid "created repository %s from %s"
+msgstr "リポジトリ %s を %s から作成"
+
+#: rhodecode/controllers/admin/repos.py:196
+#, python-format
+msgid "created repository %s"
+msgstr "リポジトリ %s を作成しました"
+
+#: rhodecode/controllers/admin/repos.py:227
+#, python-format
+msgid "error occurred during creation of repository %s"
+msgstr "リポジトリ %s を作成中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:319
+#, python-format
+msgid "Cannot delete %s it still contains attached forks"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:348
+msgid "An error occurred during deletion of repository user"
+msgstr "リポジトリユーザーの削除中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:367
+msgid "An error occurred during deletion of repository users groups"
+msgstr "リポジトリユーザーグループの削除中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:385
+msgid "An error occurred during deletion of repository stats"
+msgstr "リポジトリステートの削除中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:402
+msgid "An error occurred during cache invalidation"
+msgstr "キャッシュの無効化時にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:422
+#, fuzzy
+msgid "An error occurred during unlocking"
+msgstr "メールの保存時にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:442
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:446
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
+msgid "Token mismatch"
+msgstr "トークンが合いません"
+
+#: rhodecode/controllers/admin/repos.py:464
+msgid "Pulled from remote location"
+msgstr "リモートから取得"
+
+#: rhodecode/controllers/admin/repos.py:466
+msgid "An error occurred during pull from remote location"
+msgstr "リモートから取得中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos.py:482
+msgid "Nothing"
+msgstr "ありません"
+
+#: rhodecode/controllers/admin/repos.py:484
+#, python-format
+msgid "Marked repo %s as fork of %s"
+msgstr "%s リポジトリを %s のフォークとして印をつける"
+
+#: rhodecode/controllers/admin/repos.py:488
+msgid "An error occurred during this operation"
+msgstr "操作中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:116
+#, python-format
+msgid "created repos group %s"
+msgstr "リポジトリグループ %s を作成しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:129
+#, python-format
+msgid "error occurred during creation of repos group %s"
+msgstr "リポジトリグループ %s を作成中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:163
+#, python-format
+msgid "updated repos group %s"
+msgstr "リポジトリグループ %s を更新しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:176
+#, python-format
+msgid "error occurred during update of repos group %s"
+msgstr "リポジトリグループ %s を更新中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:194
+#, python-format
+msgid "This group contains %s repositores and cannot be deleted"
+msgstr "このグループは %s リポジトリを含んでいるため削除出来ません"
+
+#: rhodecode/controllers/admin/repos_groups.py:202
+#, python-format
+msgid "removed repos group %s"
+msgstr "リポジトリグループ %s を削除しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:208
+msgid "Cannot delete this group it still contains subgroups"
+msgstr "サブグループを含んでいるため、このグループを削除できません"
+
+#: rhodecode/controllers/admin/repos_groups.py:213
+#: rhodecode/controllers/admin/repos_groups.py:218
+#, python-format
+msgid "error occurred during deletion of repos group %s"
+msgstr "リポジトリグループの削除中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:238
+msgid "An error occurred during deletion of group user"
+msgstr "グループユーザーを削除中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/repos_groups.py:258
+msgid "An error occurred during deletion of group users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:121
+#, python-format
+msgid "Repositories successfully rescanned added: %s,removed: %s"
+msgstr "リポジトリを再度スキャンしました。 追加: %s 削除: %s"
+
+#: rhodecode/controllers/admin/settings.py:129
+msgid "Whoosh reindex task scheduled"
+msgstr "Whooshの再インデックスタスクを予定に入れました"
+
+#: rhodecode/controllers/admin/settings.py:160
+msgid "Updated application settings"
+msgstr "アプリケーション設定を更新しました"
+
+#: rhodecode/controllers/admin/settings.py:164
+#: rhodecode/controllers/admin/settings.py:275
+msgid "error occurred during updating application settings"
+msgstr "アプリケーション設定を更新中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/settings.py:200
+#, fuzzy
+msgid "Updated visualisation settings"
+msgstr "アプリケーション設定を更新しました"
+
+#: rhodecode/controllers/admin/settings.py:205
+#, fuzzy
+msgid "error occurred during updating visualisation settings"
+msgstr "アプリケーション設定を更新中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/settings.py:271
+#, fuzzy
+msgid "Updated VCS settings"
+msgstr "Mercurialの設定を更新しました"
+
+#: rhodecode/controllers/admin/settings.py:285
+msgid "Added new hook"
+msgstr "新しいフックを追加しました"
+
+#: rhodecode/controllers/admin/settings.py:297
+msgid "Updated hooks"
+msgstr "フックを更新しました"
+
+#: rhodecode/controllers/admin/settings.py:301
+msgid "error occurred during hook creation"
+msgstr "フックの作成時にエラーが発生しました"
+
+#: rhodecode/controllers/admin/settings.py:320
+msgid "Email task created"
+msgstr "メールのタスクを作成しました"
+
+#: rhodecode/controllers/admin/settings.py:375
+msgid "You can't edit this user since it's crucial for entire application"
+msgstr "このユーザーを編集出来ません。このユーザーはアプリケーションにとって必要不可欠です。"
+
+#: rhodecode/controllers/admin/settings.py:406
+msgid "Your account was updated successfully"
+msgstr "アカウントを更新しました"
+
+#: rhodecode/controllers/admin/settings.py:421
+#: rhodecode/controllers/admin/users.py:191
+#, python-format
+msgid "error occurred during update of user %s"
+msgstr "ユーザー %s の更新中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users.py:130
+#, python-format
+msgid "created user %s"
+msgstr "ユーザー %s を作成しました"
+
+#: rhodecode/controllers/admin/users.py:142
+#, python-format
+msgid "error occurred during creation of user %s"
+msgstr "ユーザー %s の作成中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users.py:171
+msgid "User updated successfully"
+msgstr "ユーザーの更新に成功しました"
+
+#: rhodecode/controllers/admin/users.py:207
+msgid "successfully deleted user"
+msgstr "ユーザーの削除に成功しました"
+
+#: rhodecode/controllers/admin/users.py:212
+msgid "An error occurred during deletion of user"
+msgstr "ユーザーの削除中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users.py:226
+msgid "You can't edit this user"
+msgstr "このユーザーは編集できません"
+
+#: rhodecode/controllers/admin/users.py:266
+msgid "Granted 'repository create' permission to user"
+msgstr "ユーザーに 'リポジトリ作成' 権限を与えました"
+
+#: rhodecode/controllers/admin/users.py:271
+msgid "Revoked 'repository create' permission to user"
+msgstr "ユーザーの 'リポジトリ作成' 権限を取り消しました"
+
+#: rhodecode/controllers/admin/users.py:277
+#, fuzzy
+msgid "Granted 'repository fork' permission to user"
+msgstr "ユーザーに 'リポジトリ作成' 権限を与えました"
+
+#: rhodecode/controllers/admin/users.py:282
+#, fuzzy
+msgid "Revoked 'repository fork' permission to user"
+msgstr "ユーザーの 'リポジトリ作成' 権限を取り消しました"
+
+#: rhodecode/controllers/admin/users.py:288
+#: rhodecode/controllers/admin/users_groups.py:255
+#, fuzzy
+msgid "An error occurred during permissions saving"
+msgstr "メールの保存時にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users.py:303
+#, python-format
+msgid "Added email %s to user"
+msgstr "ユーザーにメール %s を追加しました"
+
+#: rhodecode/controllers/admin/users.py:309
+msgid "An error occurred during email saving"
+msgstr "メールの保存時にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users.py:319
+msgid "Removed email from user"
+msgstr "ユーザーからメールを削除しました"
+
+#: rhodecode/controllers/admin/users_groups.py:84
+#, python-format
+msgid "created users group %s"
+msgstr "ユーザーグループ %s を作成しました"
+
+#: rhodecode/controllers/admin/users_groups.py:95
+#, python-format
+msgid "error occurred during creation of users group %s"
+msgstr "ユーザーグループ %s の作成中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users_groups.py:135
+#, python-format
+msgid "updated users group %s"
+msgstr "ユーザーグループ %s を更新しました"
+
+#: rhodecode/controllers/admin/users_groups.py:157
+#, python-format
+msgid "error occurred during update of users group %s"
+msgstr "ユーザーグループ %s の更新中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users_groups.py:174
+msgid "successfully deleted users group"
+msgstr "ユーザーグループ"
+
+#: rhodecode/controllers/admin/users_groups.py:179
+msgid "An error occurred during deletion of users group"
+msgstr "ユーザーグループの削除中にエラーが発生しました"
+
+#: rhodecode/controllers/admin/users_groups.py:233
+#, fuzzy
+msgid "Granted 'repository create' permission to users group"
+msgstr "ユーザーに 'リポジトリ作成' 権限を与えました"
+
+#: rhodecode/controllers/admin/users_groups.py:238
+#, fuzzy
+msgid "Revoked 'repository create' permission to users group"
+msgstr "ユーザーの 'リポジトリ作成' 権限を取り消しました"
+
+#: rhodecode/controllers/admin/users_groups.py:244
+#, fuzzy
+msgid "Granted 'repository fork' permission to users group"
+msgstr "ユーザーに 'リポジトリ作成' 権限を与えました"
+
+#: rhodecode/controllers/admin/users_groups.py:249
+#, fuzzy
+msgid "Revoked 'repository fork' permission to users group"
+msgstr "ユーザーの 'リポジトリ作成' 権限を取り消しました"
+
+#: rhodecode/lib/auth.py:499
+msgid "You need to be a registered user to perform this action"
+msgstr "このアクションを実行するためには登録ユーザーである必要があります"
+
+#: rhodecode/lib/auth.py:540
+msgid "You need to be a signed in to view this page"
+msgstr "このページを閲覧するためにはサインインが必要です"
+
+#: rhodecode/lib/diffs.py:86
+msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: rhodecode/lib/diffs.py:96
+msgid "No changes detected"
+msgstr "検出された変更はありません"
+
+#: rhodecode/lib/helpers.py:372
+#, python-format
+msgid "%a, %d %b %Y %H:%M:%S"
+msgstr "%a, %d %b %Y %H:%M:%S"
+
+#: rhodecode/lib/helpers.py:484
+msgid "True"
+msgstr "True"
+
+#: rhodecode/lib/helpers.py:488
+msgid "False"
+msgstr "False"
+
+#: rhodecode/lib/helpers.py:532
+msgid "Changeset not found"
+msgstr "リビジョンが見つかりません"
+
+#: rhodecode/lib/helpers.py:555
+#, python-format
+msgid "Show all combined changesets %s->%s"
+msgstr "%s から %s までのすべてのチェンジセットを表示"
+
+#: rhodecode/lib/helpers.py:561
+msgid "compare view"
+msgstr "比較の表示"
+
+#: rhodecode/lib/helpers.py:581
+msgid "and"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:582
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
+msgid "revisions"
+msgstr "リビジョン"
+
+#: rhodecode/lib/helpers.py:606
+msgid "fork name "
+msgstr "フォーク名 "
+
+#: rhodecode/lib/helpers.py:620
+#: rhodecode/templates/pullrequests/pullrequest_show.html:4
+#: rhodecode/templates/pullrequests/pullrequest_show.html:12
+#, python-format
+msgid "Pull request #%s"
+msgstr "プルリクエスト #%s"
+
+#: rhodecode/lib/helpers.py:626
+msgid "[deleted] repository"
+msgstr "リポジトリを[削除]"
+
+#: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
+msgid "[created] repository"
+msgstr "リポジトリを[作成]"
+
+#: rhodecode/lib/helpers.py:630
+msgid "[created] repository as fork"
+msgstr "フォークしてリポジトリを[作成]"
+
+#: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
+msgid "[forked] repository"
+msgstr "リポジトリを[フォーク]"
+
+#: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
+msgid "[updated] repository"
+msgstr "リポジトリを[更新]"
+
+#: rhodecode/lib/helpers.py:636
+msgid "[delete] repository"
+msgstr "リポジトリを[削除]"
+
+#: rhodecode/lib/helpers.py:644
+msgid "[created] user"
+msgstr "ユーザーを[作成]"
+
+#: rhodecode/lib/helpers.py:646
+msgid "[updated] user"
+msgstr "ユーザーを[更新]"
+
+#: rhodecode/lib/helpers.py:648
+msgid "[created] users group"
+msgstr "ユーザーグループを[作成]"
+
+#: rhodecode/lib/helpers.py:650
+msgid "[updated] users group"
+msgstr "ユーザーグループを[更新]"
+
+#: rhodecode/lib/helpers.py:652
+msgid "[commented] on revision in repository"
+msgstr "リポジトリのリビジョンに[コメント]"
+
+#: rhodecode/lib/helpers.py:654
+#, fuzzy
+msgid "[commented] on pull request for"
+msgstr "プルリクエストに[コメント]"
+
+#: rhodecode/lib/helpers.py:656
+#, fuzzy
+msgid "[closed] pull request for"
+msgstr "プルリクエストに[コメント]"
+
+#: rhodecode/lib/helpers.py:658
+msgid "[pushed] into"
+msgstr "[プッシュ]"
+
+#: rhodecode/lib/helpers.py:660
+msgid "[committed via RhodeCode] into repository"
+msgstr "リポジトリに[RhodeCode経由でコミット]"
+
+#: rhodecode/lib/helpers.py:662
+msgid "[pulled from remote] into repository"
+msgstr "リポジトリに[リモートからプル]"
+
+#: rhodecode/lib/helpers.py:664
+msgid "[pulled] from"
+msgstr "[プル]"
+
+#: rhodecode/lib/helpers.py:666
+msgid "[started following] repository"
+msgstr "リポジトリの[フォローを開始]"
+
+#: rhodecode/lib/helpers.py:668
+msgid "[stopped following] repository"
+msgstr "リポジトリの[フォローを停止]"
+
+#: rhodecode/lib/helpers.py:840
+#, python-format
+msgid " and %s more"
+msgstr " と %s 以上"
+
+#: rhodecode/lib/helpers.py:844
+msgid "No Files"
+msgstr ""
+
+#: rhodecode/lib/utils2.py:335
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d 年"
+
+#: rhodecode/lib/utils2.py:336
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d ヶ月"
+
+#: rhodecode/lib/utils2.py:337
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d 日"
+
+#: rhodecode/lib/utils2.py:338
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d 時間"
+
+#: rhodecode/lib/utils2.py:339
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d 分"
+
+#: rhodecode/lib/utils2.py:340
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] "%d 秒"
+
+#: rhodecode/lib/utils2.py:355
+#, python-format
+msgid "%s ago"
+msgstr "%s 前"
+
+#: rhodecode/lib/utils2.py:357
+#, python-format
+msgid "%s and %s ago"
+msgstr "%s と %s 前"
+
+#: rhodecode/lib/utils2.py:360
+msgid "just now"
+msgstr "ちょうどいま"
+
+#: rhodecode/lib/celerylib/tasks.py:269
+msgid "password reset link"
+msgstr "パスワードリセットのリンク"
+
+#: rhodecode/model/comment.py:110
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: rhodecode/model/comment.py:157
+msgid "[Mention]"
+msgstr ""
+
+#: rhodecode/model/db.py:1140
+msgid "Repository no access"
+msgstr ""
+
+#: rhodecode/model/db.py:1141
+msgid "Repository read access"
+msgstr ""
+
+#: rhodecode/model/db.py:1142
+msgid "Repository write access"
+msgstr ""
+
+#: rhodecode/model/db.py:1143
+msgid "Repository admin access"
+msgstr ""
+
+#: rhodecode/model/db.py:1145
+msgid "Repositories Group no access"
+msgstr ""
+
+#: rhodecode/model/db.py:1146
+msgid "Repositories Group read access"
+msgstr ""
+
+#: rhodecode/model/db.py:1147
+msgid "Repositories Group write access"
+msgstr ""
+
+#: rhodecode/model/db.py:1148
+msgid "Repositories Group admin access"
+msgstr ""
+
+#: rhodecode/model/db.py:1150
+msgid "RhodeCode Administrator"
+msgstr ""
+
+#: rhodecode/model/db.py:1151
+msgid "Repository creation disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1152
+msgid "Repository creation enabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1153
+msgid "Repository forking disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1154
+msgid "Repository forking enabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1155
+msgid "Register disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1156
+msgid "Register new user with RhodeCode with manual activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1159
+msgid "Register new user with RhodeCode with auto activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1579
+msgid "Not Reviewed"
+msgstr "未レビュー"
+
+#: rhodecode/model/db.py:1580
+msgid "Approved"
+msgstr "承認"
+
+#: rhodecode/model/db.py:1581
+msgid "Rejected"
+msgstr "却下"
+
+#: rhodecode/model/db.py:1582
+msgid "Under Review"
+msgstr "レビュー中"
+
+#: rhodecode/model/forms.py:43
+msgid "Please enter a login"
+msgstr "ログイン名を入力してください"
+
+#: rhodecode/model/forms.py:44
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr "%(min)i 文字以上必要です"
+
+#: rhodecode/model/forms.py:52
+msgid "Please enter a password"
+msgstr "パスワードを入力してください"
+
+#: rhodecode/model/forms.py:53
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr "%(min)i 文字以上必要です"
+
+#: rhodecode/model/notification.py:220
+msgid "commented on commit"
+msgstr "コミットに対するコメント"
+
+#: rhodecode/model/notification.py:221
+msgid "sent message"
+msgstr ""
+
+#: rhodecode/model/notification.py:222
+msgid "mentioned you"
+msgstr ""
+
+#: rhodecode/model/notification.py:223
+msgid "registered in RhodeCode"
+msgstr ""
+
+#: rhodecode/model/notification.py:224
+msgid "opened new pull request"
+msgstr ""
+
+#: rhodecode/model/notification.py:225
+msgid "commented on pull request"
+msgstr ""
+
+#: rhodecode/model/pull_request.py:84
+#, python-format
+msgid "%(user)s wants you to review pull request #%(pr_id)s"
+msgstr ""
+
+#: rhodecode/model/scm.py:535
+msgid "latest tip"
+msgstr "最新のtip"
+
+#: rhodecode/model/user.py:230
+msgid "new user registration"
+msgstr "新規ユーザー登録"
+
+#: rhodecode/model/user.py:255 rhodecode/model/user.py:277
+#: rhodecode/model/user.py:299
+msgid "You can't Edit this user since it's crucial for entire application"
+msgstr ""
+
+#: rhodecode/model/user.py:323
+msgid "You can't remove this user since it's crucial for entire application"
+msgstr ""
+
+#: rhodecode/model/user.py:329
+#, python-format
+msgid ""
+"user \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories. %s"
+msgstr ""
+
+#: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: rhodecode/model/validators.py:82
+#, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr "ユーザー名 \"%(username)s\" はすでに使われています"
+
+#: rhodecode/model/validators.py:84
+#, python-format
+msgid "Username \"%(username)s\" is forbidden"
+msgstr "ユーザー名 \"%(username)s\" は許可されていません"
+
+#: rhodecode/model/validators.py:86
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or"
+" dashes and must begin with alphanumeric character"
+msgstr "ユーザー名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットから始まる必要があります"
+
+#: rhodecode/model/validators.py:114
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr "ユーザー名 %(username)s は不正です"
+
+#: rhodecode/model/validators.py:133
+msgid "Invalid users group name"
+msgstr "不正なユーザーグループ名です"
+
+#: rhodecode/model/validators.py:134
+#, python-format
+msgid "Users group \"%(usersgroup)s\" already exists"
+msgstr "ユーザーグループ \"%(usersgroup)s\" はすでに存在します"
+
+#: rhodecode/model/validators.py:136
+msgid ""
+"users group name may only contain  alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+"ユーザーグループ名はアルファベット、アンダースコア(_)、ピリオド(.)、ダッシュ(-)しか使えません。また、アルファベットから始まる必要があります"
+" "
+
+#: rhodecode/model/validators.py:174
+msgid "Cannot assign this group as parent"
+msgstr "このグループは親にできません"
+
+#: rhodecode/model/validators.py:175
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr "グループ \"%(group_name)s\" はすでに存在します"
+
+#: rhodecode/model/validators.py:177
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr "グループ名 \"%(group_name)s\" を持つリポジトリはすでに存在します"
+
+#: rhodecode/model/validators.py:235
+msgid "Invalid characters (non-ascii) in password"
+msgstr "パスワードに利用出来ない文字列(non-ascii)です"
+
+#: rhodecode/model/validators.py:250
+msgid "Passwords do not match"
+msgstr "パスワードが一致しません"
+
+#: rhodecode/model/validators.py:267
+msgid "invalid password"
+msgstr "不正なパスワードです"
+
+#: rhodecode/model/validators.py:268
+msgid "invalid user name"
+msgstr "不正なユーザー名です"
+
+#: rhodecode/model/validators.py:269
+msgid "Your account is disabled"
+msgstr "アカウントは無効です"
+
+#: rhodecode/model/validators.py:313
+#, python-format
+msgid "Repository name %(repo)s is disallowed"
+msgstr "リポジトリ名 %(repo)s は許可されていません"
+
+#: rhodecode/model/validators.py:315
+#, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr "リポジトリ %(repo)s はすでに存在します"
+
+#: rhodecode/model/validators.py:316
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr "リポジトリ \"%(repo)s\" は グループ \"%(group)s\" にすでに存在します"
+
+#: rhodecode/model/validators.py:318
+#, python-format
+msgid "Repositories group with name \"%(repo)s\" already exists"
+msgstr "リポジトリグループ名 \"%(repo)s\" はすでに存在します"
+
+#: rhodecode/model/validators.py:431
+msgid "invalid clone url"
+msgstr "無効なクローンURIです"
+
+#: rhodecode/model/validators.py:432
+msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
+msgstr ""
+
+#: rhodecode/model/validators.py:457
+msgid "Fork have to be the same type as parent"
+msgstr "フォークは親と同じタイプの必要があります"
+
+#: rhodecode/model/validators.py:478
+msgid "This username or users group name is not valid"
+msgstr "ユーザー名かユーザーグループが不正です"
+
+#: rhodecode/model/validators.py:562
+msgid "This is not a valid path"
+msgstr "不正なパスです"
+
+#: rhodecode/model/validators.py:577
+msgid "This e-mail address is already taken"
+msgstr "このメールアドレスはすでに取得されています"
+
+#: rhodecode/model/validators.py:597
+#, python-format
+msgid "e-mail \"%(email)s\" does not exist."
+msgstr "メールアドレス \"%(email)s\" は存在しません"
+
+#: rhodecode/model/validators.py:634
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr "LDAPのこのCNに対するログイン属性は必須です。 - これは \"ユーザー名\" と同じです"
+
+#: rhodecode/model/validators.py:653
+#, python-format
+msgid "Revisions %(revs)s are already part of pull request or have set status"
+msgstr ""
+
+#: rhodecode/templates/index.html:3
+msgid "Dashboard"
+msgstr "ダッシュボード"
+
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/repo_switcher_list.html:4
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/users/user_edit_my_account.html:31
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/bookmarks/bookmarks.html:10
+#: rhodecode/templates/branches/branches.html:9
+#: rhodecode/templates/journal/journal.html:40
+#: rhodecode/templates/tags/tags.html:10
+msgid "quick filter..."
+msgstr "クイックフィルタ..."
+
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/base/base.html:221
+msgid "repositories"
+msgstr "リポジトリ"
+
+#: rhodecode/templates/index_base.html:13
+#: rhodecode/templates/index_base.html:15
+#: rhodecode/templates/admin/repos/repos.html:21
+msgid "ADD REPOSITORY"
+msgstr "リポジトリの追加"
+
+#: rhodecode/templates/index_base.html:29
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
+#: rhodecode/templates/admin/users_groups/users_group_add.html:32
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:33
+msgid "Group name"
+msgstr "グループ名"
+
+#: rhodecode/templates/index_base.html:30
+#: rhodecode/templates/index_base.html:71
+#: rhodecode/templates/index_base.html:142
+#: rhodecode/templates/index_base.html:168
+#: rhodecode/templates/admin/repos/repo_add_base.html:56
+#: rhodecode/templates/admin/repos/repo_edit.html:75
+#: rhodecode/templates/admin/repos/repos.html:72
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
+#: rhodecode/templates/forks/fork.html:59
+#: rhodecode/templates/settings/repo_settings.html:66
+#: rhodecode/templates/summary/summary.html:105
+msgid "Description"
+msgstr "説明"
+
+#: rhodecode/templates/index_base.html:40
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
+msgid "Repositories group"
+msgstr "リポジトリグループ"
+
+#: rhodecode/templates/index_base.html:70
+#: rhodecode/templates/index_base.html:166
+#: rhodecode/templates/admin/repos/repo_add_base.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:32
+#: rhodecode/templates/admin/repos/repos.html:70
+#: rhodecode/templates/admin/users/user_edit.html:192
+#: rhodecode/templates/admin/users/user_edit_my_account.html:59
+#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
+#: rhodecode/templates/bookmarks/bookmarks.html:36
+#: rhodecode/templates/bookmarks/bookmarks_data.html:6
+#: rhodecode/templates/branches/branches.html:51
+#: rhodecode/templates/files/files_browser.html:47
+#: rhodecode/templates/journal/journal.html:59
+#: rhodecode/templates/journal/journal.html:107
+#: rhodecode/templates/journal/journal.html:186
+#: rhodecode/templates/settings/repo_settings.html:31
+#: rhodecode/templates/summary/summary.html:43
+#: rhodecode/templates/summary/summary.html:123
+#: rhodecode/templates/tags/tags.html:36
+#: rhodecode/templates/tags/tags_data.html:6
+msgid "Name"
+msgstr "名前"
+
+#: rhodecode/templates/index_base.html:72
+msgid "Last change"
+msgstr "最後の変更時刻"
+
+#: rhodecode/templates/index_base.html:73
+#: rhodecode/templates/index_base.html:171
+#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/journal/journal.html:188
+msgid "Tip"
+msgstr "Tip"
+
+#: rhodecode/templates/index_base.html:74
+#: rhodecode/templates/index_base.html:173
+#: rhodecode/templates/admin/repos/repo_edit.html:121
+#: rhodecode/templates/admin/repos/repos.html:73
+msgid "Owner"
+msgstr "所有者"
+
+#: rhodecode/templates/index_base.html:75
+#: rhodecode/templates/summary/summary.html:48
+#: rhodecode/templates/summary/summary.html:51
+msgid "RSS"
+msgstr "RSS"
+
+#: rhodecode/templates/index_base.html:76
+msgid "Atom"
+msgstr "Atom"
+
+#: rhodecode/templates/index_base.html:110
+#: rhodecode/templates/index_base.html:112
+#, python-format
+msgid "Subscribe to %s rss feed"
+msgstr "%s の RSS フィードを購読"
+
+#: rhodecode/templates/index_base.html:117
+#: rhodecode/templates/index_base.html:119
+#, python-format
+msgid "Subscribe to %s atom feed"
+msgstr "%s の ATOM フィードを購読"
+
+#: rhodecode/templates/index_base.html:140
+msgid "Group Name"
+msgstr "グループ名"
+
+#: rhodecode/templates/index_base.html:158
+#: rhodecode/templates/index_base.html:198
+#: rhodecode/templates/admin/repos/repos.html:94
+#: rhodecode/templates/admin/users/user_edit_my_account.html:179
+#: rhodecode/templates/admin/users/users.html:107
+#: rhodecode/templates/bookmarks/bookmarks.html:60
+#: rhodecode/templates/branches/branches.html:77
+#: rhodecode/templates/journal/journal.html:211
+#: rhodecode/templates/tags/tags.html:60
+msgid "Click to sort ascending"
+msgstr "昇順で並び換え"
+
+#: rhodecode/templates/index_base.html:159
+#: rhodecode/templates/index_base.html:199
+#: rhodecode/templates/admin/repos/repos.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account.html:180
+#: rhodecode/templates/admin/users/users.html:108
+#: rhodecode/templates/bookmarks/bookmarks.html:61
+#: rhodecode/templates/branches/branches.html:78
+#: rhodecode/templates/journal/journal.html:212
+#: rhodecode/templates/tags/tags.html:61
+msgid "Click to sort descending"
+msgstr "降順で並び替え"
+
+#: rhodecode/templates/index_base.html:169
+msgid "Last Change"
+msgstr "最後の変更点"
+
+#: rhodecode/templates/index_base.html:200
+#: rhodecode/templates/admin/repos/repos.html:96
+#: rhodecode/templates/admin/users/user_edit_my_account.html:181
+#: rhodecode/templates/admin/users/users.html:109
+#: rhodecode/templates/bookmarks/bookmarks.html:62
+#: rhodecode/templates/branches/branches.html:79
+#: rhodecode/templates/journal/journal.html:213
+#: rhodecode/templates/tags/tags.html:62
+msgid "No records found."
+msgstr "レコードが見つかりません"
+
+#: rhodecode/templates/index_base.html:201
+#: rhodecode/templates/admin/repos/repos.html:97
+#: rhodecode/templates/admin/users/user_edit_my_account.html:182
+#: rhodecode/templates/admin/users/users.html:110
+#: rhodecode/templates/bookmarks/bookmarks.html:63
+#: rhodecode/templates/branches/branches.html:80
+#: rhodecode/templates/journal/journal.html:214
+#: rhodecode/templates/tags/tags.html:63
+msgid "Data error."
+msgstr "データエラー"
+
+#: rhodecode/templates/index_base.html:202
+#: rhodecode/templates/admin/repos/repos.html:98
+#: rhodecode/templates/admin/users/user_edit_my_account.html:183
+#: rhodecode/templates/admin/users/users.html:111
+#: rhodecode/templates/bookmarks/bookmarks.html:64
+#: rhodecode/templates/branches/branches.html:81
+#: rhodecode/templates/journal/journal.html:215
+#: rhodecode/templates/tags/tags.html:64
+msgid "Loading..."
+msgstr "読み込み中..."
+
+#: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
+msgid "Sign In"
+msgstr "サインイン"
+
+#: rhodecode/templates/login.html:21
+msgid "Sign In to"
+msgstr ""
+
+#: 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
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
+#: rhodecode/templates/base/base.html:83
+#: rhodecode/templates/summary/summary.html:122
+msgid "Username"
+msgstr "ユーザー名"
+
+#: 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
+msgid "Password"
+msgstr "パスワード"
+
+#: rhodecode/templates/login.html:50
+msgid "Remember me"
+msgstr "次回から自動的にサインイン"
+
+#: rhodecode/templates/login.html:60
+msgid "Forgot your password ?"
+msgstr "パスワードを忘れた場合はこちら"
+
+#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
+msgid "Don't have an account ?"
+msgstr "アカウントを持っていない場合はこちら"
+
+#: rhodecode/templates/password_reset.html:5
+msgid "Reset your password"
+msgstr "パスワードリセット"
+
+#: rhodecode/templates/password_reset.html:11
+msgid "Reset your password to"
+msgstr "パスワードリセット"
+
+#: rhodecode/templates/password_reset.html:21
+msgid "Email address"
+msgstr "メールアドレス"
+
+#: rhodecode/templates/password_reset.html:30
+msgid "Reset my password"
+msgstr "パスワードをリセットする"
+
+#: rhodecode/templates/password_reset.html:31
+msgid "Password reset link will be send to matching email address"
+msgstr "該当するメールアドレスにパスワードリセットのリンクを送信します"
+
+#: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
+msgid "Sign Up"
+msgstr "サインアップ"
+
+#: rhodecode/templates/register.html:11
+msgid "Sign Up to"
+msgstr "サインアップ"
+
+#: rhodecode/templates/register.html:38
+msgid "Re-enter password"
+msgstr "パスワード再入力"
+
+#: rhodecode/templates/register.html:47
+#: rhodecode/templates/admin/users/user_add.html:59
+#: rhodecode/templates/admin/users/user_edit.html:86
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
+msgid "First Name"
+msgstr "名前"
+
+#: rhodecode/templates/register.html:56
+#: rhodecode/templates/admin/users/user_add.html:68
+#: rhodecode/templates/admin/users/user_edit.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
+msgid "Last Name"
+msgstr "名字"
+
+#: rhodecode/templates/register.html:65
+#: rhodecode/templates/admin/users/user_add.html:77
+#: rhodecode/templates/admin/users/user_edit.html:104
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
+#: rhodecode/templates/summary/summary.html:124
+msgid "Email"
+msgstr "メールアドレス"
+
+#: rhodecode/templates/register.html:76
+msgid "Your account will be activated right after registration"
+msgstr "アカウントは登録後にアクティブになります"
+
+#: rhodecode/templates/register.html:78
+msgid "Your account must wait for activation by administrator"
+msgstr "アカウントは管理者のアクティベーションを待つ必要があります"
+
+#: rhodecode/templates/repo_switcher_list.html:11
+#: rhodecode/templates/admin/repos/repo_add_base.html:65
+#: rhodecode/templates/admin/repos/repo_edit.html:85
+#: rhodecode/templates/settings/repo_settings.html:76
+msgid "Private repository"
+msgstr "非公開リポジトリ"
+
+#: rhodecode/templates/repo_switcher_list.html:16
+msgid "Public repository"
+msgstr "公開リポジトリ"
+
+#: rhodecode/templates/switch_to_list.html:3
+#: rhodecode/templates/branches/branches.html:14
+msgid "branches"
+msgstr "ブランチ"
+
+#: rhodecode/templates/switch_to_list.html:10
+#: rhodecode/templates/branches/branches_data.html:57
+msgid "There are no branches yet"
+msgstr "まだブランチがありません"
+
+#: rhodecode/templates/switch_to_list.html:15
+#: rhodecode/templates/shortlog/shortlog_data.html:10
+#: rhodecode/templates/tags/tags.html:15
+msgid "tags"
+msgstr "タグ"
+
+#: rhodecode/templates/switch_to_list.html:22
+#: rhodecode/templates/tags/tags_data.html:33
+msgid "There are no tags yet"
+msgstr "まだタグがありません"
+
+#: rhodecode/templates/switch_to_list.html:28
+#: rhodecode/templates/bookmarks/bookmarks.html:15
+msgid "bookmarks"
+msgstr "ブックマーク"
+
+#: rhodecode/templates/switch_to_list.html:35
+#: rhodecode/templates/bookmarks/bookmarks_data.html:32
+msgid "There are no bookmarks yet"
+msgstr "まだブックマークがありません"
+
+#: rhodecode/templates/admin/admin.html:5
+#: rhodecode/templates/admin/admin.html:9
+msgid "Admin journal"
+msgstr "管理者ジャーナル"
+
+#: rhodecode/templates/admin/admin_log.html:6
+#: rhodecode/templates/admin/repos/repos.html:74
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
+#: rhodecode/templates/journal/journal.html:61
+#: rhodecode/templates/journal/journal.html:62
+msgid "Action"
+msgstr "アクション"
+
+#: rhodecode/templates/admin/admin_log.html:7
+msgid "Repository"
+msgstr "リポジトリ"
+
+#: rhodecode/templates/admin/admin_log.html:8
+#: rhodecode/templates/bookmarks/bookmarks.html:37
+#: rhodecode/templates/bookmarks/bookmarks_data.html:7
+#: rhodecode/templates/branches/branches.html:52
+#: rhodecode/templates/tags/tags.html:37
+#: rhodecode/templates/tags/tags_data.html:7
+msgid "Date"
+msgstr "日時"
+
+#: rhodecode/templates/admin/admin_log.html:9
+msgid "From IP"
+msgstr "アクセス元IPアドレス"
+
+#: rhodecode/templates/admin/admin_log.html:53
+msgid "No actions yet"
+msgstr "まだアクションがありません"
+
+#: rhodecode/templates/admin/ldap/ldap.html:5
+msgid "LDAP administration"
+msgstr "LDAP管理"
+
+#: rhodecode/templates/admin/ldap/ldap.html:11
+msgid "Ldap"
+msgstr "LDAP"
+
+#: rhodecode/templates/admin/ldap/ldap.html:28
+msgid "Connection settings"
+msgstr "接続設定"
+
+#: rhodecode/templates/admin/ldap/ldap.html:30
+msgid "Enable LDAP"
+msgstr "LDAPを有効にする"
+
+#: rhodecode/templates/admin/ldap/ldap.html:34
+msgid "Host"
+msgstr "ホスト"
+
+#: rhodecode/templates/admin/ldap/ldap.html:38
+msgid "Port"
+msgstr "ポート"
+
+#: rhodecode/templates/admin/ldap/ldap.html:42
+msgid "Account"
+msgstr "アカウント"
+
+#: rhodecode/templates/admin/ldap/ldap.html:50
+msgid "Connection security"
+msgstr "接続のセキュリティ"
+
+#: rhodecode/templates/admin/ldap/ldap.html:54
+msgid "Certificate Checks"
+msgstr "証明書チェック"
+
+#: rhodecode/templates/admin/ldap/ldap.html:57
+msgid "Search settings"
+msgstr "検索設定"
+
+#: rhodecode/templates/admin/ldap/ldap.html:59
+msgid "Base DN"
+msgstr "Base DN"
+
+#: rhodecode/templates/admin/ldap/ldap.html:63
+msgid "LDAP Filter"
+msgstr "LDAPフィルター"
+
+#: rhodecode/templates/admin/ldap/ldap.html:67
+msgid "LDAP Search Scope"
+msgstr "LDAP検索範囲"
+
+#: rhodecode/templates/admin/ldap/ldap.html:70
+msgid "Attribute mappings"
+msgstr "属性マッピング"
+
+#: rhodecode/templates/admin/ldap/ldap.html:72
+msgid "Login Attribute"
+msgstr "ログイン属性"
+
+#: rhodecode/templates/admin/ldap/ldap.html:76
+msgid "First Name Attribute"
+msgstr "名前(First Name)属性"
+
+#: rhodecode/templates/admin/ldap/ldap.html:80
+msgid "Last Name Attribute"
+msgstr "名字(Last Name)属性"
+
+#: rhodecode/templates/admin/ldap/ldap.html:84
+msgid "E-mail Attribute"
+msgstr "メールアドレス属性"
+
+#: rhodecode/templates/admin/ldap/ldap.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:141
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
+#: rhodecode/templates/admin/settings/hooks.html:73
+#: rhodecode/templates/admin/users/user_edit.html:129
+#: rhodecode/templates/admin/users/user_edit.html:174
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:135
+#: rhodecode/templates/settings/repo_settings.html:93
+msgid "Save"
+msgstr "保存"
+
+#: rhodecode/templates/admin/notifications/notifications.html:5
+#: rhodecode/templates/admin/notifications/notifications.html:9
+msgid "My Notifications"
+msgstr "通知"
+
+#: rhodecode/templates/admin/notifications/notifications.html:29
+msgid "All"
+msgstr "すべて"
+
+#: rhodecode/templates/admin/notifications/notifications.html:30
+msgid "Comments"
+msgstr "コメント"
+
+#: rhodecode/templates/admin/notifications/notifications.html:31
+#: rhodecode/templates/base/base.html:254
+#: rhodecode/templates/base/base.html:256
+msgid "Pull requests"
+msgstr "プルリクエスト"
+
+#: rhodecode/templates/admin/notifications/notifications.html:35
+msgid "Mark all read"
+msgstr "すべて既読にする"
+
+#: rhodecode/templates/admin/notifications/notifications_data.html:39
+msgid "No notifications here yet"
+msgstr "通知はまだありません"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:5
+#: rhodecode/templates/admin/notifications/show_notification.html:11
+msgid "Show notification"
+msgstr "通知を表示"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:9
+msgid "Notifications"
+msgstr "通知"
+
+#: rhodecode/templates/admin/permissions/permissions.html:5
+msgid "Permissions administration"
+msgstr "権限管理"
+
+#: rhodecode/templates/admin/permissions/permissions.html:11
+#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
+#: rhodecode/templates/admin/users/user_edit.html:139
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:100
+#: rhodecode/templates/settings/repo_settings.html:86
+msgid "Permissions"
+msgstr "権限設定"
+
+#: rhodecode/templates/admin/permissions/permissions.html:24
+msgid "Default permissions"
+msgstr "デフォルトの権限"
+
+#: rhodecode/templates/admin/permissions/permissions.html:31
+msgid "Anonymous access"
+msgstr "匿名アクセス"
+
+#: rhodecode/templates/admin/permissions/permissions.html:41
+msgid "Repository permission"
+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 ""
+
+#: rhodecode/templates/admin/permissions/permissions.html:50
+msgid "overwrite existing settings"
+msgstr "現在の設定を上書きする"
+
+#: rhodecode/templates/admin/permissions/permissions.html:55
+msgid "Registration"
+msgstr "登録"
+
+#: rhodecode/templates/admin/permissions/permissions.html:63
+msgid "Repository creation"
+msgstr "リポジトリ作成"
+
+#: rhodecode/templates/admin/permissions/permissions.html:71
+#, fuzzy
+msgid "Repository forking"
+msgstr "リポジトリ作成"
+
+#: rhodecode/templates/admin/permissions/permissions.html:78
+#: rhodecode/templates/admin/repos/repo_edit.html:241
+msgid "set"
+msgstr "保存"
+
+#: rhodecode/templates/admin/repos/repo_add.html:5
+#: rhodecode/templates/admin/repos/repo_add_create_repository.html:5
+msgid "Add repository"
+msgstr "リポジトリの追加"
+
+#: rhodecode/templates/admin/repos/repo_add.html:11
+#: rhodecode/templates/admin/repos/repo_edit.html:11
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
+msgid "Repositories"
+msgstr "リポジトリ"
+
+#: rhodecode/templates/admin/repos/repo_add.html:13
+msgid "add new"
+msgstr "新規追加"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:20
+#: rhodecode/templates/summary/summary.html:95
+#: rhodecode/templates/summary/summary.html:96
+msgid "Clone from"
+msgstr "クローン元"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:24
+#: rhodecode/templates/admin/repos/repo_edit.html:44
+#: rhodecode/templates/settings/repo_settings.html:43
+msgid "Optional http[s] url from which repository should be cloned."
+msgstr "オプション:クローンするリポジトリのHTTP[S]のURLを指定します"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:29
+#: rhodecode/templates/admin/repos/repo_edit.html:49
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:4
+#: rhodecode/templates/forks/fork.html:50
+#: rhodecode/templates/settings/repo_settings.html:48
+msgid "Repository group"
+msgstr "リポジトリグループ"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:33
+#: rhodecode/templates/forks/fork.html:54
+msgid "Optionaly select a group to put this repository into."
+msgstr "オプション:このリポジトリが属するグループを選択します"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:38
+#: rhodecode/templates/admin/repos/repo_edit.html:58
+msgid "Type"
+msgstr "リポジトリのタイプ"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:42
+msgid "Type of repository to create."
+msgstr "作成するリポジトリのタイプを指定します"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:47
+#: rhodecode/templates/admin/repos/repo_edit.html:66
+#: rhodecode/templates/forks/fork.html:41
+#: rhodecode/templates/settings/repo_settings.html:57
+msgid "Landing revision"
+msgstr "ランディングリビジョン"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:51
+#: rhodecode/templates/admin/repos/repo_edit.html:70
+#: rhodecode/templates/forks/fork.html:45
+#: rhodecode/templates/settings/repo_settings.html:61
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr "ファイルページ、ダウンロード、検索、READMEのデフォルトのリビジョンを指定します"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:60
+#: 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."
+msgstr "短く要点を絞ってください。長い説明にはREADMEファイルを利用してください。"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:69
+#: 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 "非公開リポジトリはコラボレーターとして明示的に追加された人でないと見つけられません"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:73
+msgid "add"
+msgstr "追加"
+
+#: rhodecode/templates/admin/repos/repo_add_create_repository.html:9
+msgid "add new repository"
+msgstr "新しいリポジトリの追加"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:5
+msgid "Edit repository"
+msgstr "リポジトリを編集"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:13
+#: rhodecode/templates/admin/users/user_edit.html:13
+#: rhodecode/templates/admin/users/user_edit.html:224
+#: rhodecode/templates/admin/users/user_edit.html:226
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:13
+#: rhodecode/templates/files/files_source.html:44
+#: rhodecode/templates/journal/journal.html:81
+msgid "edit"
+msgstr "編集"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:40
+#: rhodecode/templates/settings/repo_settings.html:39
+msgid "Clone uri"
+msgstr "クローンURI"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:53
+#: rhodecode/templates/settings/repo_settings.html:52
+msgid "Optional select a group to put this repository into."
+msgstr "オプション: このリポジトリを配置するグループを選択します"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:94
+msgid "Enable statistics"
+msgstr "統計を有効にする"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:98
+msgid "Enable statistics window on summary page."
+msgstr "概要ページの統計ウィンドウを有効にします"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:103
+msgid "Enable downloads"
+msgstr "ダウンロードを有効にする"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:107
+msgid "Enable download menu on summary page."
+msgstr "概要ページのダウンロードメニューを有効にします"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:112
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
+#, fuzzy
+msgid "Enable locking"
+msgstr "有効にする"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:116
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:126
+msgid "Change owner of this repository."
+msgstr "リポジトリの所有者を変更"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:142
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
+#: rhodecode/templates/admin/settings/settings.html:113
+#: rhodecode/templates/admin/settings/settings.html:168
+#: rhodecode/templates/admin/settings/settings.html:258
+#: rhodecode/templates/admin/users/user_edit.html:130
+#: rhodecode/templates/admin/users/user_edit.html:175
+#: rhodecode/templates/admin/users/user_edit.html:278
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:136
+#: rhodecode/templates/files/files_add.html:82
+#: rhodecode/templates/files/files_edit.html:68
+#: rhodecode/templates/pullrequests/pullrequest.html:124
+#: rhodecode/templates/settings/repo_settings.html:94
+msgid "Reset"
+msgstr "リセット"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:152
+msgid "Administration"
+msgstr "管理"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:155
+msgid "Statistics"
+msgstr "統計"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:159
+msgid "Reset current statistics"
+msgstr "現在の統計情報をリセットする"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:159
+msgid "Confirm to remove current statistics"
+msgstr "現在の統計情報をリセットしてもよろしいですか"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:162
+msgid "Fetched to rev"
+msgstr "収集するリビジョン"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:163
+msgid "Stats gathered"
+msgstr "収集した統計情報"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:171
+msgid "Remote"
+msgstr "リモート"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Pull changes from remote location"
+msgstr "リモートから変更を取り込む"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Confirm to pull changes from remote side"
+msgstr "リモートから変更を取り込んでもよろしいですか?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:186
+msgid "Cache"
+msgstr "キャッシュ"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:190
+msgid "Invalidate repository cache"
+msgstr "リポジトリのキャッシュを無効化"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:190
+msgid "Confirm to invalidate repository cache"
+msgstr "リポジトリのキャッシュを無効化してもよろしいですか?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:195
+#: rhodecode/templates/base/base.html:318
+#: rhodecode/templates/base/base.html:320
+#: rhodecode/templates/base/base.html:322
+msgid "Public journal"
+msgstr "公開ジャーナル"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:201
+msgid "Remove from public journal"
+msgstr "公開ジャーナルから削除する"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:203
+msgid "Add to public journal"
+msgstr "公開ジャーナルに追加する"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:208
+msgid ""
+"All actions made on this repository will be accessible to everyone in "
+"public journal"
+msgstr "公開ジャーナルでは、このリポジトリに対して行った操作のすべてが公開されます"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:215
+#, fuzzy
+msgid "Locking"
+msgstr "変更可能にする"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Unlock locked repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+#, fuzzy
+msgid "Confirm to unlock repository"
+msgstr "このリポジトリを削除しますか?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "lock repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+#, fuzzy
+msgid "Confirm to lock repository"
+msgstr "このリポジトリを削除しますか?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:224
+#, fuzzy
+msgid "Repository is not locked"
+msgstr "リポジトリロケーション"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:229
+msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:236
+msgid "Set as fork of"
+msgstr "フォーク元の設定"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:245
+msgid "Manually set this repository as a fork of another from the list"
+msgstr "このリポジトリをリスト中の他のリポジトリのフォークとして、手動で設定します"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:251
+#: rhodecode/templates/changeset/changeset_file_comment.html:26
+msgid "Delete"
+msgstr "削除"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+msgid "Remove this repository"
+msgstr "このリポジトリを削除"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+#: rhodecode/templates/journal/journal.html:84
+msgid "Confirm to delete this repository"
+msgstr "このリポジトリを削除しますか?"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:259
+msgid ""
+"This repository will be renamed in a special way in order to be "
+"unaccesible for RhodeCode and VCS systems.\n"
+"                         If you need fully delete it from filesystem "
+"please do it manually"
+msgstr ""
+"このリポジトリはRhodeCodeとVCSシステムからアクセスされないような名前に、特別な方法で変更されます。\n"
+"もし、ファイルシステムから完全に削除したい場合、手動で行ってください"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:3
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
+msgid "none"
+msgstr "なし"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:4
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
+msgid "read"
+msgstr "読込"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:5
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
+msgid "write"
+msgstr "書込"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:6
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
+#: rhodecode/templates/admin/users/users.html:85
+#: rhodecode/templates/base/base.html:217
+msgid "admin"
+msgstr "管理"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:7
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
+msgid "member"
+msgstr "メンバー"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
+#: rhodecode/templates/data_table/_dt_elements.html:67
+#: rhodecode/templates/journal/journal.html:132
+#: rhodecode/templates/summary/summary.html:76
+msgid "private repository"
+msgstr "非公開リポジトリ"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:19
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:28
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
+msgid "default"
+msgstr "default"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:33
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:58
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
+msgid "revoke"
+msgstr "取消"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:83
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
+msgid "Add another member"
+msgstr "別のメンバーを追加"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:97
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
+msgid "Failed to remove user"
+msgstr "ユーザーの削除に失敗しました"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:112
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
+msgid "Failed to remove users group"
+msgstr "ユーザーグループの削除に失敗しました"
+
+#: rhodecode/templates/admin/repos/repos.html:5
+msgid "Repositories administration"
+msgstr "リポジトリ管理"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:8
+msgid "Groups"
+msgstr "グループ"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:12
+msgid "with"
+msgstr "と"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:5
+msgid "Add repos group"
+msgstr "リポジトリグループを追加"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:10
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:10
+msgid "Repos groups"
+msgstr "リポジトリグループ"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:12
+msgid "add new repos group"
+msgstr "新しいリポジトリグループを追加"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:50
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:50
+msgid "Group parent"
+msgstr "親グループ"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
+#: rhodecode/templates/admin/users/user_add.html:94
+#: rhodecode/templates/admin/users_groups/users_group_add.html:49
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:90
+#: rhodecode/templates/pullrequests/pullrequest_show.html:113
+msgid "save"
+msgstr "保存"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:5
+msgid "Edit repos group"
+msgstr "リポジトリグループを編集"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:12
+msgid "edit repos group"
+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"
+msgstr ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
+msgid "Repositories groups administration"
+msgstr "リポジトリグループ管理"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:22
+msgid "ADD NEW GROUP"
+msgstr "新しいグループを追加"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
+msgid "Number of toplevel repositories"
+msgstr "トップレベルリポジトリの数"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
+#: rhodecode/templates/admin/users/users.html:87
+#: rhodecode/templates/admin/users_groups/users_groups.html:35
+msgid "action"
+msgstr "アクション"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#: rhodecode/templates/admin/users/user_edit.html:255
+#: rhodecode/templates/admin/users_groups/users_groups.html:44
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#: rhodecode/templates/data_table/_dt_elements.html:103
+msgid "delete"
+msgstr "削除"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#, python-format
+msgid "Confirm to delete this group: %s"
+msgstr "グループ %s を削除してもよろしいですか"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
+msgid "There are no repositories groups yet"
+msgstr "まだリポジトリグループがありません"
+
+#: rhodecode/templates/admin/settings/hooks.html:5
+#: rhodecode/templates/admin/settings/settings.html:5
+msgid "Settings administration"
+msgstr "設定管理"
+
+#: rhodecode/templates/admin/settings/hooks.html:9
+#: rhodecode/templates/admin/settings/settings.html:9
+#: rhodecode/templates/settings/repo_settings.html:13
+msgid "Settings"
+msgstr "設定"
+
+#: rhodecode/templates/admin/settings/hooks.html:24
+msgid "Built in hooks - read only"
+msgstr "組み込みフック - 読み込み専用"
+
+#: rhodecode/templates/admin/settings/hooks.html:40
+msgid "Custom hooks"
+msgstr "カスタムフック"
+
+#: rhodecode/templates/admin/settings/hooks.html:56
+msgid "remove"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/hooks.html:88
+msgid "Failed to remove hook"
+msgstr "フックの削除に失敗しました"
+
+#: rhodecode/templates/admin/settings/settings.html:24
+msgid "Remap and rescan repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:32
+msgid "rescan option"
+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 ""
+
+#: rhodecode/templates/admin/settings/settings.html:39
+msgid "destroy old data"
+msgstr "古いデータを削除する"
+
+#: rhodecode/templates/admin/settings/settings.html:41
+msgid ""
+"Rescan repositories location for new repositories. Also deletes obsolete "
+"if `destroy` flag is checked "
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:46
+msgid "Rescan repositories"
+msgstr "リポジトリを再チェック"
+
+#: rhodecode/templates/admin/settings/settings.html:52
+msgid "Whoosh indexing"
+msgstr "Whooshインデックス"
+
+#: rhodecode/templates/admin/settings/settings.html:60
+msgid "index build option"
+msgstr "インデックス作成時の設定"
+
+#: rhodecode/templates/admin/settings/settings.html:65
+msgid "build from scratch"
+msgstr "一度削除してから再度インデックスを作成"
+
+#: rhodecode/templates/admin/settings/settings.html:71
+msgid "Reindex"
+msgstr "再インデックス"
+
+#: rhodecode/templates/admin/settings/settings.html:77
+msgid "Global application settings"
+msgstr "アプリケーション全体の設定"
+
+#: rhodecode/templates/admin/settings/settings.html:86
+msgid "Application name"
+msgstr "アプリケーション名"
+
+#: rhodecode/templates/admin/settings/settings.html:95
+msgid "Realm text"
+msgstr "Realmテキスト"
+
+#: rhodecode/templates/admin/settings/settings.html:104
+msgid "GA code"
+msgstr "GAコード"
+
+#: rhodecode/templates/admin/settings/settings.html:112
+#: rhodecode/templates/admin/settings/settings.html:167
+#: rhodecode/templates/admin/settings/settings.html:257
+msgid "Save settings"
+msgstr "設定を保存"
+
+#: rhodecode/templates/admin/settings/settings.html:119
+#, fuzzy
+msgid "Visualisation settings"
+msgstr "アプリケーション全体の設定"
+
+#: rhodecode/templates/admin/settings/settings.html:128
+#, fuzzy
+msgid "Icons"
+msgstr "オプション"
+
+#: rhodecode/templates/admin/settings/settings.html:133
+msgid "Show public repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:137
+#, fuzzy
+msgid "Show private repo icon on repositories"
+msgstr "非公開リポジトリ"
+
+#: rhodecode/templates/admin/settings/settings.html:144
+#, fuzzy
+msgid "Meta-Tagging"
+msgstr "設定"
+
+#: rhodecode/templates/admin/settings/settings.html:149
+msgid "Stylify recognised metatags:"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:176
+#, fuzzy
+msgid "VCS settings"
+msgstr "設定"
+
+#: rhodecode/templates/admin/settings/settings.html:185
+msgid "Web"
+msgstr "Web"
+
+#: rhodecode/templates/admin/settings/settings.html:190
+#, fuzzy
+msgid "require ssl for vcs operations"
+msgstr "プッシュにSSLを必須とする"
+
+#: rhodecode/templates/admin/settings/settings.html:192
+msgid ""
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
+"will return HTTP Error 406: Not Acceptable"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:198
+msgid "Hooks"
+msgstr "フック"
+
+#: rhodecode/templates/admin/settings/settings.html:203
+msgid "Update repository after push (hg update)"
+msgstr "プッシュ後にリポジトリをを更新する (hg update)"
+
+#: rhodecode/templates/admin/settings/settings.html:207
+msgid "Show repository size after push"
+msgstr "プッシュ後にリポジトリのサイズを表示する"
+
+#: rhodecode/templates/admin/settings/settings.html:211
+msgid "Log user push commands"
+msgstr "ユーザーのプッシュコマンドを記録する"
+
+#: rhodecode/templates/admin/settings/settings.html:215
+msgid "Log user pull commands"
+msgstr "ユーザーのプルコマンドを記録する"
+
+#: rhodecode/templates/admin/settings/settings.html:219
+msgid "advanced setup"
+msgstr "高度な設定"
+
+#: rhodecode/templates/admin/settings/settings.html:224
+#, fuzzy
+msgid "Mercurial Extensions"
+msgstr "Mercurialリポジトリ"
+
+#: rhodecode/templates/admin/settings/settings.html:229
+msgid "largefiles extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:233
+msgid "hgsubversion extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:235
+msgid ""
+"Requires hgsubversion library installed. Allows clonning from svn remote "
+"locations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:245
+msgid "Repositories location"
+msgstr "リポジトリロケーション"
+
+#: rhodecode/templates/admin/settings/settings.html:250
+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 "これはアプリケーションの重要な設定です。本当に変更が必要でしょうか。もし、変更した場合、変更を反映さ競るためにアプリケーションを再起動する必要があります。変更可能にするにはこのラベルをクリックして下さい"
+
+#: rhodecode/templates/admin/settings/settings.html:251
+msgid "unlock"
+msgstr "変更可能にする"
+
+#: rhodecode/templates/admin/settings/settings.html:252
+msgid ""
+"Location where repositories are stored. After changing this value a "
+"restart, and rescan is required"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:272
+msgid "Test Email"
+msgstr "テストメール"
+
+#: rhodecode/templates/admin/settings/settings.html:280
+msgid "Email to"
+msgstr "送信先"
+
+#: rhodecode/templates/admin/settings/settings.html:288
+msgid "Send"
+msgstr "送る"
+
+#: rhodecode/templates/admin/settings/settings.html:294
+msgid "System Info and Packages"
+msgstr "システム情報とパッケージ"
+
+#: rhodecode/templates/admin/settings/settings.html:297
+msgid "show"
+msgstr "表示"
+
+#: rhodecode/templates/admin/users/user_add.html:5
+msgid "Add user"
+msgstr "ユーザーを追加"
+
+#: rhodecode/templates/admin/users/user_add.html:10
+#: rhodecode/templates/admin/users/user_edit.html:11
+msgid "Users"
+msgstr "ユーザー"
+
+#: rhodecode/templates/admin/users/user_add.html:12
+msgid "add new user"
+msgstr "新しいユーザーを追加"
+
+#: rhodecode/templates/admin/users/user_add.html:50
+msgid "Password confirmation"
+msgstr "パスワード再入力"
+
+#: rhodecode/templates/admin/users/user_add.html:86
+#: rhodecode/templates/admin/users/user_edit.html:113
+#: rhodecode/templates/admin/users_groups/users_group_add.html:41
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:42
+msgid "Active"
+msgstr "アクティブ"
+
+#: rhodecode/templates/admin/users/user_edit.html:5
+msgid "Edit user"
+msgstr "ユーザー編集"
+
+#: rhodecode/templates/admin/users/user_edit.html:34
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
+msgid "Change your avatar at"
+msgstr "アイコンを変えられます : "
+
+#: rhodecode/templates/admin/users/user_edit.html:35
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
+msgid "Using"
+msgstr "メールアドレス:"
+
+#: rhodecode/templates/admin/users/user_edit.html:43
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
+msgid "API key"
+msgstr "APIキー"
+
+#: rhodecode/templates/admin/users/user_edit.html:59
+msgid "LDAP DN"
+msgstr "LDAP DN"
+
+#: rhodecode/templates/admin/users/user_edit.html:68
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
+msgid "New password"
+msgstr "新しいパスワード"
+
+#: rhodecode/templates/admin/users/user_edit.html:77
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
+msgid "New password confirmation"
+msgstr "新しいパスワード 再入力"
+
+#: rhodecode/templates/admin/users/user_edit.html:147
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:108
+#, fuzzy
+msgid "Inherit default permissions"
+msgstr "デフォルトの権限"
+
+#: rhodecode/templates/admin/users/user_edit.html:152
+#: 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 ""
+
+#: rhodecode/templates/admin/users/user_edit.html:158
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
+msgid "Create repositories"
+msgstr "リポジトリを作成する"
+
+#: rhodecode/templates/admin/users/user_edit.html:166
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:127
+#, fuzzy
+msgid "Fork repositories"
+msgstr "リポジトリ"
+
+#: rhodecode/templates/admin/users/user_edit.html:186
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
+#, fuzzy
+msgid "Nothing here yet"
+msgstr "通知はまだありません"
+
+#: rhodecode/templates/admin/users/user_edit.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account.html:60
+#: rhodecode/templates/admin/users/user_edit_my_account.html:194
+msgid "Permission"
+msgstr "権限"
+
+#: rhodecode/templates/admin/users/user_edit.html:194
+#, fuzzy
+msgid "Edit Permission"
+msgstr "リポジトリの権限"
+
+#: rhodecode/templates/admin/users/user_edit.html:243
+msgid "Email addresses"
+msgstr "メールアドレス"
+
+#: rhodecode/templates/admin/users/user_edit.html:256
+#, python-format
+msgid "Confirm to delete this email: %s"
+msgstr "このメールを削除してよろしいですか: %s"
+
+#: rhodecode/templates/admin/users/user_edit.html:270
+msgid "New email address"
+msgstr "新しいメールアドレス"
+
+#: rhodecode/templates/admin/users/user_edit.html:277
+msgid "Add"
+msgstr "追加"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:5
+#: rhodecode/templates/base/base.html:124
+msgid "My account"
+msgstr "アカウント"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:9
+msgid "My Account"
+msgstr "アカウント"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:35
+msgid "My permissions"
+msgstr "権限"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:38
+#: rhodecode/templates/journal/journal.html:41
+msgid "My repos"
+msgstr "リポジトリ"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:41
+#, fuzzy
+msgid "My pull requests"
+msgstr "すべてのプルリクエスト"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:45
+#, fuzzy
+msgid "Add repo"
+msgstr "新規追加"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
+msgid "Opened by me"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
+#, fuzzy, python-format
+msgid "Pull request #%s opened on %s"
+msgstr "プルリクエスト #%s"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
+#, fuzzy
+msgid "Confirm to delete this pull request"
+msgstr "このリポジトリを削除しますか?"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
+msgid "I participate in"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
+#, python-format
+msgid "Pull request #%s opened by %s on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
+#: rhodecode/templates/bookmarks/bookmarks.html:40
+#: rhodecode/templates/bookmarks/bookmarks_data.html:9
+#: rhodecode/templates/branches/branches.html:55
+#: rhodecode/templates/journal/journal.html:60
+#: rhodecode/templates/tags/tags.html:40
+#: rhodecode/templates/tags/tags_data.html:9
+msgid "Revision"
+msgstr "リビジョン"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/journal/journal.html:81
+msgid "private"
+msgstr "非公開"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr "このリポジトリを削除しますか? : %s"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
+#: rhodecode/templates/journal/journal.html:94
+msgid "No repositories yet"
+msgstr "まだリポジトリがありません"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
+#: rhodecode/templates/journal/journal.html:96
+msgid "create one now"
+msgstr "今すぐ作成する"
+
+#: rhodecode/templates/admin/users/users.html:5
+msgid "Users administration"
+msgstr "ユーザー管理"
+
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/base/base.html:223
+msgid "users"
+msgstr "ユーザー"
+
+#: rhodecode/templates/admin/users/users.html:23
+msgid "ADD NEW USER"
+msgstr "新しいユーザーを追加"
+
+#: rhodecode/templates/admin/users/users.html:77
+msgid "username"
+msgstr "ユーザー名"
+
+#: rhodecode/templates/admin/users/users.html:80
+#, fuzzy
+msgid "firstname"
+msgstr "名前"
+
+#: rhodecode/templates/admin/users/users.html:81
+msgid "lastname"
+msgstr "名字"
+
+#: rhodecode/templates/admin/users/users.html:82
+msgid "last login"
+msgstr "最終ログイン日"
+
+#: rhodecode/templates/admin/users/users.html:84
+#: rhodecode/templates/admin/users_groups/users_groups.html:34
+msgid "active"
+msgstr "アクティブ"
+
+#: rhodecode/templates/admin/users/users.html:86
+#: rhodecode/templates/base/base.html:226
+msgid "ldap"
+msgstr "LDAP"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:5
+msgid "Add users group"
+msgstr "ユーザーグループを追加"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:10
+#: rhodecode/templates/admin/users_groups/users_groups.html:9
+msgid "Users groups"
+msgstr "ユーザーグループ"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:12
+msgid "add new users group"
+msgstr "新しいユーザーグループを追加"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:5
+msgid "Edit users group"
+msgstr "ユーザーグループを編集"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:11
+msgid "UsersGroups"
+msgstr "ユーザーグループ"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:50
+msgid "Members"
+msgstr "メンバー"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:58
+msgid "Choosen group members"
+msgstr "グループメンバーを選ぶ"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:61
+msgid "Remove all elements"
+msgstr "全ての要素を削除"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:75
+msgid "Available members"
+msgstr "有効なメンバー"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:79
+msgid "Add all elements"
+msgstr "全ての要素を追加"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:146
+msgid "Group members"
+msgstr "グループメンバー"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:5
+msgid "Users groups administration"
+msgstr "ユーザーグループ管理"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:23
+msgid "ADD NEW USER GROUP"
+msgstr "新しいユーザーグループを追加"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:32
+msgid "group name"
+msgstr "グループ名"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:33
+#: rhodecode/templates/base/root.html:46
+msgid "members"
+msgstr "メンバー"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:45
+#, python-format
+msgid "Confirm to delete this users group: %s"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:41
+msgid "Submit a bug"
+msgstr "バグレポート"
+
+#: rhodecode/templates/base/base.html:77
+msgid "Login to your account"
+msgstr "ログイン"
+
+#: rhodecode/templates/base/base.html:100
+msgid "Forgot password ?"
+msgstr "パスワードを忘れた場合はこちら"
+
+#: rhodecode/templates/base/base.html:107
+msgid "Log In"
+msgstr "ログイン"
+
+#: rhodecode/templates/base/base.html:118
+msgid "Inbox"
+msgstr "受信箱"
+
+#: rhodecode/templates/base/base.html:122
+#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/base/base.html:302
+#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/bookmarks/bookmarks.html:11
+#: rhodecode/templates/branches/branches.html:10
+#: rhodecode/templates/changelog/changelog.html:10
+#: rhodecode/templates/changeset/changeset.html:10
+#: rhodecode/templates/changeset/changeset_range.html:9
+#: rhodecode/templates/compare/compare_diff.html:9
+#: rhodecode/templates/files/file_diff.html:8
+#: rhodecode/templates/files/files.html:8
+#: 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/pullrequests/pullrequest.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
+#: rhodecode/templates/settings/repo_settings.html:9
+#: rhodecode/templates/shortlog/shortlog.html:10
+#: rhodecode/templates/summary/summary.html:8
+#: rhodecode/templates/tags/tags.html:11
+msgid "Home"
+msgstr "ホーム"
+
+#: rhodecode/templates/base/base.html:123
+#: rhodecode/templates/base/base.html:309
+#: rhodecode/templates/base/base.html:311
+#: rhodecode/templates/base/base.html:313
+#: rhodecode/templates/journal/journal.html:4
+#: rhodecode/templates/journal/journal.html:21
+#: rhodecode/templates/journal/public_journal.html:4
+msgid "Journal"
+msgstr "ジャーナル"
+
+#: rhodecode/templates/base/base.html:125
+msgid "Log Out"
+msgstr "ログアウト"
+
+#: rhodecode/templates/base/base.html:144
+msgid "Switch repository"
+msgstr "リポジトリの切り替え"
+
+#: rhodecode/templates/base/base.html:146
+msgid "Products"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:152
+#: rhodecode/templates/base/base.html:182
+msgid "loading..."
+msgstr "読み込み中..."
+
+#: rhodecode/templates/base/base.html:158
+#: rhodecode/templates/base/base.html:160
+#: rhodecode/templates/base/base.html:162
+#: rhodecode/templates/data_table/_dt_elements.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:17
+#: rhodecode/templates/data_table/_dt_elements.html:19
+msgid "Summary"
+msgstr "要約"
+
+#: rhodecode/templates/base/base.html:166
+#: rhodecode/templates/base/base.html:168
+#: rhodecode/templates/base/base.html:170
+#: rhodecode/templates/changelog/changelog.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:23
+#: rhodecode/templates/data_table/_dt_elements.html:25
+#: rhodecode/templates/data_table/_dt_elements.html:27
+msgid "Changelog"
+msgstr "履歴"
+
+#: rhodecode/templates/base/base.html:175
+#: rhodecode/templates/base/base.html:177
+#: rhodecode/templates/base/base.html:179
+msgid "Switch to"
+msgstr "ブランチの切り替え"
+
+#: rhodecode/templates/base/base.html:186
+#: rhodecode/templates/base/base.html:188
+#: rhodecode/templates/base/base.html:190
+#: rhodecode/templates/data_table/_dt_elements.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:33
+#: rhodecode/templates/data_table/_dt_elements.html:35
+msgid "Files"
+msgstr "ファイル"
+
+#: rhodecode/templates/base/base.html:195
+#: rhodecode/templates/base/base.html:199
+msgid "Options"
+msgstr "オプション"
+
+#: rhodecode/templates/base/base.html:204
+#: rhodecode/templates/base/base.html:206
+#: rhodecode/templates/base/base.html:227
+msgid "settings"
+msgstr "設定"
+
+#: rhodecode/templates/base/base.html:209
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/forks/fork.html:13
+msgid "fork"
+msgstr "フォーク"
+
+#: rhodecode/templates/base/base.html:211
+#: rhodecode/templates/changelog/changelog.html:40
+msgid "Open new pull request"
+msgstr "新しいプルリクエストを作成"
+
+#: rhodecode/templates/base/base.html:213
+msgid "search"
+msgstr "検索"
+
+#: rhodecode/templates/base/base.html:222
+msgid "repositories groups"
+msgstr "リポジトリグループ"
+
+#: rhodecode/templates/base/base.html:224
+msgid "users groups"
+msgstr "ユーザーグループ"
+
+#: rhodecode/templates/base/base.html:225
+msgid "permissions"
+msgstr "権限"
+
+#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:240
+msgid "Followers"
+msgstr "フォロワー"
+
+#: rhodecode/templates/base/base.html:246
+#: rhodecode/templates/base/base.html:248
+msgid "Forks"
+msgstr "フォーク"
+
+#: rhodecode/templates/base/base.html:327
+#: rhodecode/templates/base/base.html:329
+#: rhodecode/templates/base/base.html:331
+#: rhodecode/templates/search/search.html:52
+msgid "Search"
+msgstr "検索"
+
+#: rhodecode/templates/base/root.html:42
+msgid "add another comment"
+msgstr "別のコメントを追加"
+
+#: rhodecode/templates/base/root.html:43
+#: rhodecode/templates/journal/journal.html:120
+#: rhodecode/templates/summary/summary.html:57
+msgid "Stop following this repository"
+msgstr "このリポジトリのフォローをやめる"
+
+#: rhodecode/templates/base/root.html:44
+#: rhodecode/templates/summary/summary.html:61
+msgid "Start following this repository"
+msgstr "このリポジトリのフォローする"
+
+#: rhodecode/templates/base/root.html:45
+msgid "Group"
+msgstr "グループ"
+
+#: rhodecode/templates/base/root.html:47
+msgid "search truncated"
+msgstr "検索結果は省略されています"
+
+#: rhodecode/templates/base/root.html:48
+msgid "no matching files"
+msgstr "マッチするファイルはありません"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:5
+#, python-format
+msgid "%s Bookmarks"
+msgstr "%s ブックマーク"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:39
+#: rhodecode/templates/bookmarks/bookmarks_data.html:8
+#: rhodecode/templates/branches/branches.html:54
+#: rhodecode/templates/tags/tags.html:39
+#: rhodecode/templates/tags/tags_data.html:8
+msgid "Author"
+msgstr "作成者"
+
+#: rhodecode/templates/branches/branches.html:5
+#, python-format
+msgid "%s Branches"
+msgstr "%s ブランチ"
+
+#: rhodecode/templates/branches/branches.html:29
+msgid "Compare branches"
+msgstr "ブランチの比較"
+
+#: rhodecode/templates/branches/branches.html:57
+#: rhodecode/templates/compare/compare_diff.html:5
+#: rhodecode/templates/compare/compare_diff.html:13
+msgid "Compare"
+msgstr "比較"
+
+#: rhodecode/templates/branches/branches_data.html:6
+msgid "name"
+msgstr "名前"
+
+#: rhodecode/templates/branches/branches_data.html:7
+msgid "date"
+msgstr "日付"
+
+#: rhodecode/templates/branches/branches_data.html:8
+#: rhodecode/templates/shortlog/shortlog_data.html:8
+msgid "author"
+msgstr "作成者"
+
+#: rhodecode/templates/branches/branches_data.html:9
+#: rhodecode/templates/shortlog/shortlog_data.html:5
+msgid "revision"
+msgstr "リビジョン"
+
+#: rhodecode/templates/branches/branches_data.html:10
+msgid "compare"
+msgstr "比較"
+
+#: rhodecode/templates/changelog/changelog.html:6
+#, python-format
+msgid "%s Changelog"
+msgstr "%s チェンジログ"
+
+#: rhodecode/templates/changelog/changelog.html:15
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:19
+#, python-format
+msgid "compare fork with %s"
+msgstr "%s とフォークを比較"
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:21
+msgid "Compare fork"
+msgstr "フォークを比較"
+
+#: rhodecode/templates/changelog/changelog.html:46
+msgid "Show"
+msgstr "表示"
+
+#: rhodecode/templates/changelog/changelog.html:72
+#: rhodecode/templates/summary/summary.html:364
+msgid "show more"
+msgstr "もっと表示"
+
+#: rhodecode/templates/changelog/changelog.html:76
+msgid "Affected number of files, click to show more details"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:89
+#: rhodecode/templates/changeset/changeset.html:38
+#: rhodecode/templates/changeset/changeset_file_comment.html:20
+#: rhodecode/templates/changeset/changeset_range.html:46
+msgid "Changeset status"
+msgstr "リビジョンステータス"
+
+#: rhodecode/templates/changelog/changelog.html:92
+msgid "Click to open associated pull request"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:102
+#: rhodecode/templates/changeset/changeset.html:78
+msgid "Parent"
+msgstr "親リビジョン"
+
+#: rhodecode/templates/changelog/changelog.html:108
+#: rhodecode/templates/changeset/changeset.html:84
+msgid "No parents"
+msgstr "親リビジョンはありません"
+
+#: rhodecode/templates/changelog/changelog.html:113
+#: rhodecode/templates/changeset/changeset.html:88
+msgid "merge"
+msgstr "マージ"
+
+#: rhodecode/templates/changelog/changelog.html:116
+#: rhodecode/templates/changeset/changeset.html:91
+#: rhodecode/templates/files/files.html:29
+#: rhodecode/templates/files/files_add.html:33
+#: rhodecode/templates/files/files_edit.html:33
+#: rhodecode/templates/shortlog/shortlog_data.html:9
+msgid "branch"
+msgstr "ブランチ"
+
+#: rhodecode/templates/changelog/changelog.html:122
+msgid "bookmark"
+msgstr "ブックマーク"
+
+#: rhodecode/templates/changelog/changelog.html:128
+#: rhodecode/templates/changeset/changeset.html:96
+msgid "tag"
+msgstr "タグ"
+
+#: rhodecode/templates/changelog/changelog.html:164
+msgid "Show selected changes __S -> __E"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:255
+msgid "There are no changes yet"
+msgstr "まだ変更がありません"
+
+#: rhodecode/templates/changelog/changelog_details.html:4
+#: rhodecode/templates/changeset/changeset.html:66
+msgid "removed"
+msgstr "削除"
+
+#: rhodecode/templates/changelog/changelog_details.html:5
+#: rhodecode/templates/changeset/changeset.html:67
+msgid "changed"
+msgstr "変更"
+
+#: rhodecode/templates/changelog/changelog_details.html:6
+#: rhodecode/templates/changeset/changeset.html:68
+msgid "added"
+msgstr "追加"
+
+#: rhodecode/templates/changelog/changelog_details.html:8
+#: rhodecode/templates/changelog/changelog_details.html:9
+#: rhodecode/templates/changelog/changelog_details.html:10
+#: rhodecode/templates/changeset/changeset.html:70
+#: rhodecode/templates/changeset/changeset.html:71
+#: rhodecode/templates/changeset/changeset.html:72
+#, python-format
+msgid "affected %s files"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset.html:6
+#, python-format
+msgid "%s Changeset"
+msgstr "%s チェンジセット"
+
+#: rhodecode/templates/changeset/changeset.html:14
+msgid "Changeset"
+msgstr "チェンジセット"
+
+#: rhodecode/templates/changeset/changeset.html:43
+#: rhodecode/templates/changeset/diff_block.html:20
+msgid "raw diff"
+msgstr "差分を表示"
+
+#: rhodecode/templates/changeset/changeset.html:44
+#: rhodecode/templates/changeset/diff_block.html:21
+msgid "download diff"
+msgstr "差分をダウンロード"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d コメント"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "(%d inline)"
+msgid_plural "(%d inline)"
+msgstr[0] "(%d インライン)"
+
+#: rhodecode/templates/changeset/changeset.html:103
+#, python-format
+msgid "%s files affected with %s insertions and %s deletions:"
+msgstr "%s ファイルに影響。 %s 個の追加と %s 個の削除:"
+
+#: rhodecode/templates/changeset/changeset.html:119
+msgid "Changeset was too big and was cut off..."
+msgstr "チェンジセットが大きすぎるため、省略しました"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:42
+msgid "Submitting..."
+msgstr "サブミット中..."
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:45
+msgid "Commenting on line {1}."
+msgstr "{1} 行目にコメント"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:46
+#: rhodecode/templates/changeset/changeset_file_comment.html:121
+#, python-format
+msgid "Comments parsed using %s syntax with %s support."
+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"
+msgstr "テキスト内で @username を使うと、この RhodeCode のユーザーに通知を送信します"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:59
+#: rhodecode/templates/changeset/changeset_file_comment.html:138
+msgid "Comment"
+msgstr "コメント"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:60
+#: rhodecode/templates/changeset/changeset_file_comment.html:71
+msgid "Hide"
+msgstr "隠す"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "You need to be logged in to comment."
+msgstr "コメントするにはログインが必要です"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "Login now"
+msgstr "今すぐログインする"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:118
+msgid "Leave a comment"
+msgstr "コメントを残す"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "Check this to change current status of code-review for this changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "change status"
+msgstr "ステータスを変更する"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:140
+msgid "Comment and close"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr "%s チェンジセット"
+
+#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/templates/compare/compare_diff.html:29
+msgid "Compare View"
+msgstr "比較ビュー"
+
+#: rhodecode/templates/changeset/changeset_range.html:54
+#: rhodecode/templates/compare/compare_diff.html:41
+#: rhodecode/templates/pullrequests/pullrequest_show.html:69
+msgid "Files affected"
+msgstr ""
+
+#: rhodecode/templates/changeset/diff_block.html:19
+msgid "diff"
+msgstr "差分"
+
+#: rhodecode/templates/changeset/diff_block.html:27
+msgid "show inline comments"
+msgstr "インラインコメントを表示"
+
+#: rhodecode/templates/compare/compare_cs.html:5
+msgid "No changesets"
+msgstr "チェンジセットはありません"
+
+#: rhodecode/templates/compare/compare_diff.html:37
+msgid "Outgoing changesets"
+msgstr "送信可能なチェンジセット"
+
+#: rhodecode/templates/data_table/_dt_elements.html:39
+#: rhodecode/templates/data_table/_dt_elements.html:41
+#: rhodecode/templates/data_table/_dt_elements.html:43
+msgid "Fork"
+msgstr "フォーク"
+
+#: rhodecode/templates/data_table/_dt_elements.html:60
+#: rhodecode/templates/journal/journal.html:126
+#: rhodecode/templates/summary/summary.html:68
+msgid "Mercurial repository"
+msgstr "Mercurialリポジトリ"
+
+#: rhodecode/templates/data_table/_dt_elements.html:62
+#: rhodecode/templates/journal/journal.html:128
+#: rhodecode/templates/summary/summary.html:71
+msgid "Git repository"
+msgstr "Gitリポジトリ"
+
+#: rhodecode/templates/data_table/_dt_elements.html:69
+#: rhodecode/templates/journal/journal.html:134
+#: rhodecode/templates/summary/summary.html:78
+msgid "public repository"
+msgstr "公開リポジトリ"
+
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/summary/summary.html:87
+#: rhodecode/templates/summary/summary.html:88
+msgid "Fork of"
+msgstr "フォーク元: "
+
+#: rhodecode/templates/data_table/_dt_elements.html:92
+msgid "No changesets yet"
+msgstr "まだチェンジセットがありません"
+
+#: rhodecode/templates/data_table/_dt_elements.html:104
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr "このユーザーを本当に削除してよろしいですか?: %s"
+
+#: rhodecode/templates/email_templates/main.html:8
+msgid "This is an notification from RhodeCode."
+msgstr "RhodeCodeからの通知があります"
+
+#: rhodecode/templates/errors/error_document.html:46
+#, python-format
+msgid "You will be redirected to %s in %s seconds"
+msgstr ""
+
+#: rhodecode/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File diff"
+msgstr "%s ファイル差分"
+
+#: rhodecode/templates/files/file_diff.html:12
+msgid "File diff"
+msgstr "ファイル差分"
+
+#: rhodecode/templates/files/files.html:4
+#: rhodecode/templates/files/files.html:72
+#, fuzzy, python-format
+msgid "%s files"
+msgstr "%s ファイル"
+
+#: rhodecode/templates/files/files.html:12
+#: rhodecode/templates/summary/summary.html:340
+msgid "files"
+msgstr "ファイル"
+
+#: rhodecode/templates/files/files_add.html:4
+#: rhodecode/templates/files/files_edit.html:4
+#, python-format
+msgid "%s Edit file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:19
+msgid "add file"
+msgstr "ファイルを追加"
+
+#: rhodecode/templates/files/files_add.html:40
+msgid "Add new file"
+msgstr "新しいファイルを追加"
+
+#: rhodecode/templates/files/files_add.html:45
+msgid "File Name"
+msgstr "ファイル名"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:58
+msgid "or"
+msgstr "または"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:54
+msgid "Upload file"
+msgstr "アップロード"
+
+#: rhodecode/templates/files/files_add.html:58
+msgid "Create new file"
+msgstr "新しいファイルを作成"
+
+#: rhodecode/templates/files/files_add.html:63
+#: rhodecode/templates/files/files_edit.html:39
+#: rhodecode/templates/files/files_ypjax.html:3
+msgid "Location"
+msgstr "場所"
+
+#: rhodecode/templates/files/files_add.html:67
+msgid "use / to separate directories"
+msgstr "ディレクトリの区切りには / を使います"
+
+#: rhodecode/templates/files/files_add.html:77
+#: rhodecode/templates/files/files_edit.html:63
+#: rhodecode/templates/shortlog/shortlog_data.html:6
+msgid "commit message"
+msgstr "コミットメッセージ"
+
+#: rhodecode/templates/files/files_add.html:81
+#: rhodecode/templates/files/files_edit.html:67
+msgid "Commit changes"
+msgstr "変更をコミット"
+
+#: rhodecode/templates/files/files_browser.html:13
+msgid "view"
+msgstr "表示"
+
+#: rhodecode/templates/files/files_browser.html:14
+msgid "previous revision"
+msgstr "前のリビジョン"
+
+#: rhodecode/templates/files/files_browser.html:16
+msgid "next revision"
+msgstr "次のリビジョン"
+
+#: rhodecode/templates/files/files_browser.html:23
+msgid "follow current branch"
+msgstr "このブランチで追跡"
+
+#: rhodecode/templates/files/files_browser.html:27
+msgid "search file list"
+msgstr "ファイル一覧を検索"
+
+#: rhodecode/templates/files/files_browser.html:31
+#: rhodecode/templates/shortlog/shortlog_data.html:65
+msgid "add new file"
+msgstr "新しいファイルを追加"
+
+#: rhodecode/templates/files/files_browser.html:35
+msgid "Loading file list..."
+msgstr "ファイル一覧を読み込み中..."
+
+#: rhodecode/templates/files/files_browser.html:48
+msgid "Size"
+msgstr "サイズ"
+
+#: rhodecode/templates/files/files_browser.html:49
+msgid "Mimetype"
+msgstr "Mimetype"
+
+#: rhodecode/templates/files/files_browser.html:50
+msgid "Last Revision"
+msgstr "最後のリビジョン"
+
+#: rhodecode/templates/files/files_browser.html:51
+msgid "Last modified"
+msgstr "最終更新日"
+
+#: rhodecode/templates/files/files_browser.html:52
+msgid "Last commiter"
+msgstr "最後の作成者"
+
+#: rhodecode/templates/files/files_edit.html:19
+msgid "edit file"
+msgstr "ファイルを編集"
+
+#: rhodecode/templates/files/files_edit.html:49
+#: rhodecode/templates/files/files_source.html:38
+msgid "show annotation"
+msgstr "アノテーションを表示"
+
+#: rhodecode/templates/files/files_edit.html:50
+#: rhodecode/templates/files/files_source.html:40
+#: rhodecode/templates/files/files_source.html:68
+msgid "show as raw"
+msgstr "元のファイルを表示"
+
+#: rhodecode/templates/files/files_edit.html:51
+#: rhodecode/templates/files/files_source.html:41
+msgid "download as raw"
+msgstr "元のファイルをダウンロード"
+
+#: rhodecode/templates/files/files_edit.html:54
+msgid "source"
+msgstr "ソース"
+
+#: rhodecode/templates/files/files_edit.html:59
+msgid "Editing file"
+msgstr "ファイルを編集"
+
+#: rhodecode/templates/files/files_source.html:2
+msgid "History"
+msgstr "変更履歴"
+
+#: rhodecode/templates/files/files_source.html:9
+msgid "diff to revision"
+msgstr "このリビジョンの差分を見る"
+
+#: rhodecode/templates/files/files_source.html:10
+msgid "show at revision"
+msgstr "このリビジョンを見る"
+
+#: rhodecode/templates/files/files_source.html:14
+#, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] "%s 作成者"
+
+#: rhodecode/templates/files/files_source.html:36
+msgid "show source"
+msgstr "ソースを表示"
+
+#: rhodecode/templates/files/files_source.html:59
+#, python-format
+msgid "Binary file (%s)"
+msgstr "バイナリファイル (%s)"
+
+#: rhodecode/templates/files/files_source.html:68
+msgid "File is too big to display"
+msgstr "表示するには大きすぎるファイルです"
+
+#: rhodecode/templates/files/files_source.html:124
+msgid "Selection link"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr "アノテーション"
+
+#: rhodecode/templates/files/files_ypjax.html:15
+msgid "Go back"
+msgstr "戻る"
+
+#: rhodecode/templates/files/files_ypjax.html:16
+msgid "No files at given path"
+msgstr "そのパスにはファイルはありません"
+
+#: rhodecode/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr "%s フォロワー"
+
+#: rhodecode/templates/followers/followers.html:13
+msgid "followers"
+msgstr "フォロワー"
+
+#: rhodecode/templates/followers/followers_data.html:12
+msgid "Started following -"
+msgstr "フォロー開始日 -"
+
+#: rhodecode/templates/forks/fork.html:5
+#, python-format
+msgid "%s Fork"
+msgstr "%s フォーク"
+
+#: rhodecode/templates/forks/fork.html:31
+msgid "Fork name"
+msgstr "フォーク名"
+
+#: rhodecode/templates/forks/fork.html:68
+msgid "Private"
+msgstr "非公開"
+
+#: rhodecode/templates/forks/fork.html:77
+msgid "Copy permissions"
+msgstr "権限のコピー"
+
+#: rhodecode/templates/forks/fork.html:81
+msgid "Copy permissions from forked repository"
+msgstr "フォーク元リポジトリから権限をコピーします"
+
+#: rhodecode/templates/forks/fork.html:86
+msgid "Update after clone"
+msgstr "クローン後にupdateする"
+
+#: rhodecode/templates/forks/fork.html:90
+msgid "Checkout source after making a clone"
+msgstr "クローンした後にソースをチェックアウトします"
+
+#: rhodecode/templates/forks/fork.html:94
+msgid "fork this repository"
+msgstr "このリポジトリをフォーク"
+
+#: rhodecode/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
+msgstr "%s フォーク"
+
+#: rhodecode/templates/forks/forks.html:13
+msgid "forks"
+msgstr "フォーク"
+
+#: rhodecode/templates/forks/forks_data.html:17
+msgid "forked"
+msgstr "フォークしました"
+
+#: rhodecode/templates/forks/forks_data.html:38
+msgid "There are no forks yet"
+msgstr "まだフォークがありません"
+
+#: rhodecode/templates/journal/journal.html:13
+msgid "ATOM journal feed"
+msgstr "ATOM ジャーナルフィード"
+
+#: rhodecode/templates/journal/journal.html:14
+msgid "RSS journal feed"
+msgstr "RSS ジャーナルフィード"
+
+#: rhodecode/templates/journal/journal.html:24
+#: rhodecode/templates/pullrequests/pullrequest.html:27
+msgid "Refresh"
+msgstr "更新"
+
+#: rhodecode/templates/journal/journal.html:27
+#: rhodecode/templates/journal/public_journal.html:24
+msgid "RSS feed"
+msgstr "RSSフィード"
+
+#: rhodecode/templates/journal/journal.html:30
+#: rhodecode/templates/journal/public_journal.html:27
+msgid "ATOM feed"
+msgstr "ATOMフィード"
+
+#: rhodecode/templates/journal/journal.html:41
+msgid "Watched"
+msgstr "ウォッチ"
+
+#: rhodecode/templates/journal/journal.html:46
+msgid "ADD"
+msgstr "追加"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "following user"
+msgstr "フォローしているユーザー"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "user"
+msgstr "ユーザー"
+
+#: rhodecode/templates/journal/journal.html:147
+msgid "You are not following any users or repositories"
+msgstr "まだどのユーザーもリポジトリもフォローしていません"
+
+#: rhodecode/templates/journal/journal_data.html:47
+msgid "No entries yet"
+msgstr "まだエントリがありません"
+
+#: rhodecode/templates/journal/public_journal.html:13
+msgid "ATOM public journal feed"
+msgstr "ATOM 公開ジャーナルフィード"
+
+#: rhodecode/templates/journal/public_journal.html:14
+msgid "RSS public journal feed"
+msgstr "RSS 公開ジャーナルフィード"
+
+#: rhodecode/templates/journal/public_journal.html:21
+msgid "Public Journal"
+msgstr "公開ジャーナル"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:4
+#: rhodecode/templates/pullrequests/pullrequest.html:12
+msgid "New pull request"
+msgstr "新しいプルリクエスト"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:28
+msgid "refresh overview"
+msgstr "概要の更新"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:66
+msgid "Detailed compare view"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:70
+#: rhodecode/templates/pullrequests/pullrequest_show.html:82
+msgid "Pull request reviewers"
+msgstr "プルリクエストレビュアー"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:79
+#: rhodecode/templates/pullrequests/pullrequest_show.html:94
+#, fuzzy
+msgid "owner"
+msgstr "所有者"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:91
+#: rhodecode/templates/pullrequests/pullrequest_show.html:109
+#, fuzzy
+msgid "Add reviewer to this pull request."
+msgstr "新しいプルリクエスト"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:97
+msgid "Create new pull request"
+msgstr "新しいプルリクエストを作成"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:106
+#: rhodecode/templates/pullrequests/pullrequest_show.html:25
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
+msgid "Title"
+msgstr "タイトル"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:115
+msgid "description"
+msgstr "説明"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:123
+msgid "Send pull request"
+msgstr "プルリクエストを送る"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:23
+#, python-format
+msgid "Closed %s"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:31
+#, fuzzy
+msgid "Status"
+msgstr "ステータスを変更する"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:36
+msgid "Pull request status"
+msgstr "プルリクエストステータス"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:44
+#, fuzzy
+msgid "Still not reviewed by"
+msgstr "未レビュー"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:47
+#, fuzzy, python-format
+msgid "%d reviewer"
+msgid_plural "%d reviewers"
+msgstr[0] "レビュー中"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:54
+msgid "Created on"
+msgstr "作成日"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:61
+msgid "Compare view"
+msgstr "比較ビュー"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:65
+msgid "Incoming changesets"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
+#, fuzzy
+msgid "all pull requests"
+msgstr "すべてのプルリクエスト"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
+msgid "All pull requests"
+msgstr "すべてのプルリクエスト"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
+msgid "Closed"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:6
+#, python-format
+msgid "Search \"%s\" in repository: %s"
+msgstr "\"%s\" を %s リポジトリから検索"
+
+#: rhodecode/templates/search/search.html:8
+#, python-format
+msgid "Search \"%s\" in all repositories"
+msgstr "\"%s\" を全てのリポジトリから検索"
+
+#: rhodecode/templates/search/search.html:12
+#: rhodecode/templates/search/search.html:32
+#, python-format
+msgid "Search in repository: %s"
+msgstr "%s リポジトリから検索"
+
+#: rhodecode/templates/search/search.html:14
+#: rhodecode/templates/search/search.html:34
+msgid "Search in all repositories"
+msgstr "全てのリポジトリから検索"
+
+#: rhodecode/templates/search/search.html:48
+msgid "Search term"
+msgstr "検索キーワード"
+
+#: rhodecode/templates/search/search.html:60
+msgid "Search in"
+msgstr "検索対象"
+
+#: rhodecode/templates/search/search.html:63
+msgid "File contents"
+msgstr "ファイル内容"
+
+#: rhodecode/templates/search/search.html:64
+#, fuzzy
+msgid "Commit messages"
+msgstr "コミットメッセージ"
+
+#: rhodecode/templates/search/search.html:65
+msgid "File names"
+msgstr "ファイル名"
+
+#: rhodecode/templates/search/search_commit.html:35
+#: rhodecode/templates/search/search_content.html:21
+#: rhodecode/templates/search/search_path.html:15
+msgid "Permission denied"
+msgstr "権限がありません"
+
+#: rhodecode/templates/settings/repo_settings.html:5
+#, python-format
+msgid "%s Settings"
+msgstr "%s 設定"
+
+#: rhodecode/templates/shortlog/shortlog.html:5
+#, python-format
+msgid "%s Shortlog"
+msgstr "%s 短いログ"
+
+#: rhodecode/templates/shortlog/shortlog.html:14
+msgid "shortlog"
+msgstr "ログ"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:7
+msgid "age"
+msgstr "経過時間"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:18
+msgid "No commit message"
+msgstr "コミットメッセージが有りません"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:62
+msgid "Add or upload files directly via RhodeCode"
+msgstr "RhodeCode経由で直接ファイルを追加またはアップロード"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:71
+msgid "Push new repo"
+msgstr "新しいリポジトリをプッシュ"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:79
+msgid "Existing repository?"
+msgstr "存在するリポジトリをプッシュ"
+
+#: rhodecode/templates/summary/summary.html:4
+#, python-format
+msgid "%s Summary"
+msgstr "%s 要約"
+
+#: rhodecode/templates/summary/summary.html:12
+msgid "summary"
+msgstr "要約"
+
+#: rhodecode/templates/summary/summary.html:20
+#, python-format
+msgid "repo %s ATOM feed"
+msgstr "リポジトリ %s ATOM フィード"
+
+#: rhodecode/templates/summary/summary.html:21
+#, python-format
+msgid "repo %s RSS feed"
+msgstr "リポジトリ %s RSS フィード"
+
+#: rhodecode/templates/summary/summary.html:49
+#: rhodecode/templates/summary/summary.html:52
+msgid "ATOM"
+msgstr "ATOM"
+
+#: rhodecode/templates/summary/summary.html:82
+#, python-format
+msgid "Non changable ID %s"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:87
+msgid "public"
+msgstr "公開"
+
+#: rhodecode/templates/summary/summary.html:95
+msgid "remote clone"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:116
+msgid "Contact"
+msgstr "コンタクト"
+
+#: rhodecode/templates/summary/summary.html:130
+msgid "Clone url"
+msgstr "クローンURL"
+
+#: rhodecode/templates/summary/summary.html:133
+msgid "Show by Name"
+msgstr "名前で表示"
+
+#: rhodecode/templates/summary/summary.html:134
+msgid "Show by ID"
+msgstr "IDで表示"
+
+#: rhodecode/templates/summary/summary.html:142
+msgid "Trending files"
+msgstr "トレンドファイル"
+
+#: rhodecode/templates/summary/summary.html:150
+#: rhodecode/templates/summary/summary.html:166
+#: rhodecode/templates/summary/summary.html:194
+msgid "enable"
+msgstr "有効にする"
+
+#: rhodecode/templates/summary/summary.html:158
+msgid "Download"
+msgstr "ダウンロード"
+
+#: rhodecode/templates/summary/summary.html:162
+msgid "There are no downloads yet"
+msgstr "まだダウンロードがありません"
+
+#: rhodecode/templates/summary/summary.html:164
+msgid "Downloads are disabled for this repository"
+msgstr "このリポジトリのダウンロードは無効化されています"
+
+#: rhodecode/templates/summary/summary.html:170
+msgid "Download as zip"
+msgstr "ZIPとしてダウンロード"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "Check this to download archive with subrepos"
+msgstr "チェックするとダウンロードアーカイブにサブリポジトリが含まれます"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "with subrepos"
+msgstr "サブリポジトリを含む"
+
+#: rhodecode/templates/summary/summary.html:186
+msgid "Commit activity by day / author"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:197
+msgid "Stats gathered: "
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:218
+msgid "Shortlog"
+msgstr "ログ"
+
+#: rhodecode/templates/summary/summary.html:220
+msgid "Quick start"
+msgstr "クイックスタート"
+
+#: rhodecode/templates/summary/summary.html:233
+#, python-format
+msgid "Readme file at revision '%s'"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:236
+msgid "Permalink to this readme"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:293
+#, python-format
+msgid "Download %s as %s"
+msgstr "%s を %sとしてダウンロード"
+
+#: rhodecode/templates/summary/summary.html:650
+msgid "commits"
+msgstr "コミット"
+
+#: rhodecode/templates/summary/summary.html:651
+msgid "files added"
+msgstr "追加されたファイル"
+
+#: rhodecode/templates/summary/summary.html:652
+msgid "files changed"
+msgstr "変更されたファイル"
+
+#: rhodecode/templates/summary/summary.html:653
+msgid "files removed"
+msgstr "削除されたファイル"
+
+#: rhodecode/templates/summary/summary.html:656
+msgid "commit"
+msgstr "コミット"
+
+#: rhodecode/templates/summary/summary.html:657
+msgid "file added"
+msgstr "追加されたファイル"
+
+#: rhodecode/templates/summary/summary.html:658
+msgid "file changed"
+msgstr "変更されたファイル"
+
+#: rhodecode/templates/summary/summary.html:659
+msgid "file removed"
+msgstr "削除されたファイル"
+
+#: rhodecode/templates/tags/tags.html:5
+#, python-format
+msgid "%s Tags"
+msgstr "%s タグ"
+
diff --git a/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.mo b/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.mo
index c72e89a83ef050f6877f1bc2c4a8bd7d782bccc3..a28cc6841a8372c932a5fb23c4e497a92e71c005
GIT binary patch
literal 42556
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

diff --git a/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.po
--- a/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.po
+++ b/rhodecode/i18n/pt_BR/LC_MESSAGES/rhodecode.po
@@ -1,14 +1,14 @@
 # Portuguese (Brazil) translations for RhodeCode.
-# Copyright (C) 2011 Augusto Herrmann
+# Copyright (C) 2011-2012 Augusto Herrmann
 # This file is distributed under the same license as the RhodeCode project.
-# Augusto Herrmann , 2011.
+# Augusto Herrmann , 2012.
 #
 msgid ""
 msgstr ""
 "Project-Id-Version: RhodeCode 1.2.0\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-09-14 15:50-0300\n"
-"PO-Revision-Date: 2011-09-14 15:53-0300\n"
+"POT-Creation-Date: 2012-09-02 20:30+0200\n"
+"PO-Revision-Date: 2012-05-22 16:47-0300\n"
 "Last-Translator: Augusto Herrmann \n"
 "Language-Team: pt_BR \n"
 "Plural-Forms: nplurals=2; plural=(n > 1)\n"
@@ -17,21 +17,38 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.6\n"
 
-#: rhodecode/controllers/changeset.py:108
-#: rhodecode/controllers/changeset.py:149
-#: rhodecode/controllers/changeset.py:216
-#: rhodecode/controllers/changeset.py:229
+#: rhodecode/controllers/changelog.py:94
+msgid "All Branches"
+msgstr "Todos os Ramos"
+
+#: rhodecode/controllers/changeset.py:83
+msgid "show white space"
+msgstr "mostrar espaços em branco"
+
+#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+msgid "ignore white space"
+msgstr "ignorar espaços em branco"
+
+#: rhodecode/controllers/changeset.py:157
+#, python-format
+msgid "%s line context"
+msgstr "contexto de %s linhas"
+
+#: rhodecode/controllers/changeset.py:333
+#: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
 msgid "binary file"
 msgstr "arquivo binário"
 
-#: rhodecode/controllers/changeset.py:123
-#: rhodecode/controllers/changeset.py:168
-msgid "Changeset is to big and was cut off, see raw changeset instead"
-msgstr "Conjunto de mudanças é grande demais e foi cortado, em vez disso veja o conjunto de mudanças bruto"
-
-#: rhodecode/controllers/changeset.py:159
-msgid "Diff is to big and was cut off, see raw diff instead"
-msgstr "Diff é grande demais e foi cortado, em vez disso veja diff bruto"
+#: rhodecode/controllers/changeset.py:408
+msgid ""
+"Changing status on a changeset associated witha closed pull request is "
+"not allowed"
+msgstr ""
+
+#: rhodecode/controllers/compare.py:69
+#, fuzzy
+msgid "There are no changesets yet"
+msgstr "Ainda não há alteações"
 
 #: rhodecode/controllers/error.py:69
 msgid "Home page"
@@ -39,7 +56,9 @@ msgstr "Página inicial"
 
 #: rhodecode/controllers/error.py:98
 msgid "The request could not be understood by the server due to malformed syntax."
-msgstr "A requisição não pôde ser compreendida pelo servidor devido à sintaxe mal formada"
+msgstr ""
+"A requisição não pôde ser compreendida pelo servidor devido à sintaxe mal"
+" formada"
 
 #: rhodecode/controllers/error.py:101
 msgid "Unauthorized access to resource"
@@ -54,250 +73,325 @@ msgid "The resource could not be found"
 msgstr "O recurso não pôde ser encontrado"
 
 #: rhodecode/controllers/error.py:107
-msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
-msgstr "O servidor encontrou uma condição inesperada que o impediu de satisfazer a requisição"
-
-#: rhodecode/controllers/feed.py:48
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+"O servidor encontrou uma condição inesperada que o impediu de satisfazer "
+"a requisição"
+
+#: rhodecode/controllers/feed.py:49
 #, python-format
 msgid "Changes on %s repository"
 msgstr "Alterações no repositório %s"
 
-#: rhodecode/controllers/feed.py:49
+#: rhodecode/controllers/feed.py:50
 #, python-format
 msgid "%s %s feed"
 msgstr "%s - feed %s"
 
-#: rhodecode/controllers/files.py:72
-msgid "There are no files yet"
-msgstr "Ainda não há arquivos"
-
-#: rhodecode/controllers/files.py:262
+#: rhodecode/controllers/feed.py:75
+#, fuzzy
+msgid "commited on"
+msgstr "commit"
+
+#: rhodecode/controllers/files.py:84
+#, fuzzy
+msgid "click here to add new file"
+msgstr "adicionar novo arquivo"
+
+#: rhodecode/controllers/files.py:85
+#, python-format
+msgid "There are no files yet %s"
+msgstr "Ainda não há arquivos %s"
+
+#: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
+#, python-format
+msgid "This repository is has been locked by %s on %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:266
 #, python-format
 msgid "Edited %s via RhodeCode"
 msgstr "Editado %s via RhodeCode"
 
-#: rhodecode/controllers/files.py:267
-#: rhodecode/templates/files/file_diff.html:40
+#: rhodecode/controllers/files.py:271
 msgid "No changes"
 msgstr "Sem alterações"
 
-#: rhodecode/controllers/files.py:278
+#: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "Commit realizado com sucesso para %s"
 
-#: rhodecode/controllers/files.py:283
+#: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
 msgid "Error occurred during commit"
 msgstr "Ocorreu um erro ao realizar commit"
 
-#: rhodecode/controllers/files.py:308
+#: rhodecode/controllers/files.py:318
+#, python-format
+msgid "Added %s via RhodeCode"
+msgstr "Adicionado %s via RhodeCode"
+
+#: rhodecode/controllers/files.py:332
+msgid "No content"
+msgstr "Nenhum conteúdo"
+
+#: rhodecode/controllers/files.py:336
+msgid "No filename"
+msgstr "Nenhum nomes de arquivo"
+
+#: rhodecode/controllers/files.py:378
 msgid "downloads disabled"
 msgstr "downloads desabilitados"
 
-#: rhodecode/controllers/files.py:313
+#: rhodecode/controllers/files.py:389
 #, python-format
 msgid "Unknown revision %s"
 msgstr "Revisão desconhecida %s"
 
-#: rhodecode/controllers/files.py:315
+#: rhodecode/controllers/files.py:391
 msgid "Empty repository"
 msgstr "Repositório vazio"
 
-#: rhodecode/controllers/files.py:317
+#: rhodecode/controllers/files.py:393
 msgid "Unknown archive type"
 msgstr "Arquivo de tipo desconhecido"
 
-#: rhodecode/controllers/files.py:385
-#: rhodecode/controllers/files.py:398
-msgid "Binary file"
-msgstr "Arquivo binário"
-
-#: rhodecode/controllers/files.py:417
-#: rhodecode/templates/changeset/changeset_range.html:4
-#: rhodecode/templates/changeset/changeset_range.html:12
-#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/controllers/files.py:494
+#: rhodecode/templates/changeset/changeset_range.html:13
+#: rhodecode/templates/changeset/changeset_range.html:31
 msgid "Changesets"
 msgstr "Conjuntos de mudanças"
 
-#: rhodecode/controllers/files.py:418
-#: rhodecode/controllers/summary.py:175
-#: rhodecode/templates/branches/branches.html:5
-#: rhodecode/templates/summary/summary.html:690
+#: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
+#: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
 msgid "Branches"
 msgstr "Ramos"
 
-#: rhodecode/controllers/files.py:419
-#: rhodecode/controllers/summary.py:176
-#: rhodecode/templates/summary/summary.html:679
-#: rhodecode/templates/tags/tags.html:5
+#: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
+#: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
 msgid "Tags"
 msgstr "Etiquetas"
 
-#: rhodecode/controllers/journal.py:50
+#: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
+#, 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 ""
+"repositório %s não está mapeado ao bd. Talvez ele tenha sido criado ou "
+"renomeado a partir do sistema de arquivos. Por favor execute a aplicação "
+"outra vez para varrer novamente por repositórios"
+
+#: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
 #, python-format
-msgid "%s public journal %s feed"
-msgstr "diário público de %s - feed %s"
-
-#: rhodecode/controllers/journal.py:178
-#: rhodecode/controllers/journal.py:212
-#: rhodecode/templates/admin/repos/repo_edit.html:171
-#: rhodecode/templates/base/base.html:50
-msgid "Public journal"
+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 ""
+"repositório %s não está mapeado ao bd. Talvez ele tenha sido criado ou "
+"renomeado a partir do sistema de arquivos. Por favor execute a aplicação "
+"outra vez para varrer novamente por repositórios"
+
+#: rhodecode/controllers/forks.py:167
+#, python-format
+msgid "forked %s repository as %s"
+msgstr "bifurcado repositório %s como %s"
+
+#: rhodecode/controllers/forks.py:181
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr "Ocorreu um erro ao bifurcar o repositório %s"
+
+#: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
+#, fuzzy
+msgid "public journal"
 msgstr "Diário público"
 
-#: rhodecode/controllers/login.py:111
+#: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
+#: rhodecode/templates/base/base.html:220
+msgid "journal"
+msgstr "diário"
+
+#: rhodecode/controllers/login.py:143
 msgid "You have successfully registered into rhodecode"
 msgstr "Você se registrou com sucesso no rhodecode"
 
-#: rhodecode/controllers/login.py:133
+#: rhodecode/controllers/login.py:164
 msgid "Your password reset link was sent"
 msgstr "Seu link de reinicialização de senha foi enviado"
 
-#: rhodecode/controllers/login.py:155
-msgid "Your password reset was successful, new password has been sent to your email"
-msgstr "Sua reinicialização de senha foi bem sucedida, sua senha foi enviada ao seu e-mail"
-
-#: rhodecode/controllers/search.py:109
+#: rhodecode/controllers/login.py:184
+msgid ""
+"Your password reset was successful, new password has been sent to your "
+"email"
+msgstr ""
+"Sua reinicialização de senha foi bem sucedida, sua senha foi enviada ao "
+"seu e-mail"
+
+#: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
+msgid "Bookmarks"
+msgstr "Marcadores"
+
+#: rhodecode/controllers/pullrequests.py:158
+msgid "Pull request requires a title with min. 3 chars"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:160
+#, fuzzy
+msgid "error during creation of pull request"
+msgstr "ocorreu um erro ao criar o usuário %s"
+
+#: rhodecode/controllers/pullrequests.py:181
+#, fuzzy
+msgid "Successfully opened new pull request"
+msgstr "usuário excluído com sucesso"
+
+#: rhodecode/controllers/pullrequests.py:184
+#, fuzzy
+msgid "Error occurred during sending pull request"
+msgstr "ocorreu um erro ao criar o repositório %s"
+
+#: rhodecode/controllers/pullrequests.py:217
+#, fuzzy
+msgid "Successfully deleted pull request"
+msgstr "usuário excluído com sucesso"
+
+#: rhodecode/controllers/search.py:131
 msgid "Invalid search query. Try quoting it."
 msgstr "Consulta de busca inválida. Tente usar aspas."
 
-#: rhodecode/controllers/search.py:114
+#: rhodecode/controllers/search.py:136
 msgid "There is no index to search in. Please run whoosh indexer"
 msgstr "Não há índice onde pesquisa. Por favor execute o indexador whoosh"
 
-#: rhodecode/controllers/search.py:118
+#: rhodecode/controllers/search.py:140
 msgid "An error occurred during this search operation"
 msgstr "Ocorreu um erro durante essa operação de busca"
 
-#: rhodecode/controllers/settings.py:61
-#: rhodecode/controllers/settings.py:171
-#, python-format
-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 "repositório %s não está mapeado ao bd. Talvez ele tenha sido criado ou renomeado a partir do sistema de arquivos. Por favor execute a aplicação outra vez para varrer novamente por repositórios"
-
-#: rhodecode/controllers/settings.py:109
-#: rhodecode/controllers/admin/repos.py:239
+#: rhodecode/controllers/settings.py:107
+#: rhodecode/controllers/admin/repos.py:266
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "Repositório %s atualizado com sucesso"
 
-#: rhodecode/controllers/settings.py:126
-#: rhodecode/controllers/admin/repos.py:257
+#: rhodecode/controllers/settings.py:125
+#: rhodecode/controllers/admin/repos.py:284
 #, python-format
 msgid "error occurred during update of repository %s"
 msgstr "ocorreu um erro ao atualizar o repositório %s"
 
-#: rhodecode/controllers/settings.py:144
-#: rhodecode/controllers/admin/repos.py:275
+#: rhodecode/controllers/settings.py:143
+#: rhodecode/controllers/admin/repos.py:302
 #, python-format
-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 "repositório %s não está mapeado ao bd. Talvez ele tenha sido movido ou renomeado a partir do sistema de arquivos. Por favor execute a aplicação outra vez para varrer novamente por repositórios"
-
-#: rhodecode/controllers/settings.py:156
-#: rhodecode/controllers/admin/repos.py:287
+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 ""
+"repositório %s não está mapeado ao bd. Talvez ele tenha sido movido ou "
+"renomeado a partir do sistema de arquivos. Por favor execute a aplicação "
+"outra vez para varrer novamente por repositórios"
+
+#: rhodecode/controllers/settings.py:155
+#: rhodecode/controllers/admin/repos.py:314
 #, python-format
 msgid "deleted repository %s"
 msgstr "excluído o repositório %s"
 
 #: rhodecode/controllers/settings.py:159
-#: rhodecode/controllers/admin/repos.py:297
-#: rhodecode/controllers/admin/repos.py:303
+#: rhodecode/controllers/admin/repos.py:324
+#: rhodecode/controllers/admin/repos.py:330
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr "Ocorreu um erro durante a exclusão de %s"
 
-#: rhodecode/controllers/settings.py:193
-#, python-format
-msgid "forked %s repository as %s"
-msgstr "bifurcado repositório %s como %s"
-
-#: rhodecode/controllers/settings.py:211
-#, python-format
-msgid "An error occurred during repository forking %s"
-msgstr "Ocorreu um erro ao bifurcar o repositório %s"
-
-#: rhodecode/controllers/summary.py:123
+#: rhodecode/controllers/summary.py:138
 msgid "No data loaded yet"
 msgstr "Ainda não há dados carregados"
 
-#: rhodecode/controllers/summary.py:126
+#: rhodecode/controllers/summary.py:142
+#: rhodecode/templates/summary/summary.html:148
 msgid "Statistics are disabled for this repository"
 msgstr "As estatísticas estão desabillitadas para este repositório"
 
-#: rhodecode/controllers/admin/ldap_settings.py:49
+#: rhodecode/controllers/admin/ldap_settings.py:50
 msgid "BASE"
 msgstr "BASE"
 
-#: rhodecode/controllers/admin/ldap_settings.py:50
+#: rhodecode/controllers/admin/ldap_settings.py:51
 msgid "ONELEVEL"
 msgstr "UMNÍVEL"
 
-#: rhodecode/controllers/admin/ldap_settings.py:51
+#: rhodecode/controllers/admin/ldap_settings.py:52
 msgid "SUBTREE"
 msgstr "SUBÁRVORE"
 
-#: rhodecode/controllers/admin/ldap_settings.py:55
+#: rhodecode/controllers/admin/ldap_settings.py:56
 msgid "NEVER"
 msgstr "NUNCA"
 
-#: rhodecode/controllers/admin/ldap_settings.py:56
+#: rhodecode/controllers/admin/ldap_settings.py:57
 msgid "ALLOW"
 msgstr "PERMITIR"
 
-#: rhodecode/controllers/admin/ldap_settings.py:57
-msgid "TRY"
-msgstr "TENTAR"
-
 #: rhodecode/controllers/admin/ldap_settings.py:58
-msgid "DEMAND"
-msgstr "EXIGIR"
+msgid "TRY"
+msgstr "TENTAR"
 
 #: rhodecode/controllers/admin/ldap_settings.py:59
+msgid "DEMAND"
+msgstr "EXIGIR"
+
+#: rhodecode/controllers/admin/ldap_settings.py:60
 msgid "HARD"
 msgstr "DIFÍCIL"
 
-#: rhodecode/controllers/admin/ldap_settings.py:63
+#: rhodecode/controllers/admin/ldap_settings.py:64
 msgid "No encryption"
 msgstr "Sem criptografia"
 
-#: rhodecode/controllers/admin/ldap_settings.py:64
+#: rhodecode/controllers/admin/ldap_settings.py:65
 msgid "LDAPS connection"
 msgstr "Conexão LDAPS"
 
-#: rhodecode/controllers/admin/ldap_settings.py:65
+#: rhodecode/controllers/admin/ldap_settings.py:66
 msgid "START_TLS on LDAP connection"
 msgstr "START_TLS na conexão LDAP"
 
-#: rhodecode/controllers/admin/ldap_settings.py:115
+#: rhodecode/controllers/admin/ldap_settings.py:126
 msgid "Ldap settings updated successfully"
 msgstr "Configurações de LDAP atualizadas com sucesso"
 
-#: rhodecode/controllers/admin/ldap_settings.py:120
+#: rhodecode/controllers/admin/ldap_settings.py:130
 msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
 msgstr "Não foi possível ativar LDAP. A biblioteca \"python-ldap\" está faltando."
 
-#: rhodecode/controllers/admin/ldap_settings.py:134
+#: rhodecode/controllers/admin/ldap_settings.py:147
 msgid "error occurred during update of ldap settings"
 msgstr "ocorreu um erro ao atualizar as configurações de LDAP"
 
-#: rhodecode/controllers/admin/permissions.py:56
+#: rhodecode/controllers/admin/permissions.py:59
 msgid "None"
 msgstr "Nenhum"
 
-#: rhodecode/controllers/admin/permissions.py:57
+#: rhodecode/controllers/admin/permissions.py:60
 msgid "Read"
 msgstr "Ler"
 
-#: rhodecode/controllers/admin/permissions.py:58
+#: rhodecode/controllers/admin/permissions.py:61
 msgid "Write"
 msgstr "Gravar"
 
-#: rhodecode/controllers/admin/permissions.py:59
+#: rhodecode/controllers/admin/permissions.py:62
 #: rhodecode/templates/admin/ldap/ldap.html:9
 #: rhodecode/templates/admin/permissions/permissions.html:9
 #: rhodecode/templates/admin/repos/repo_add.html:9
 #: rhodecode/templates/admin/repos/repo_edit.html:9
-#: rhodecode/templates/admin/repos/repos.html:10
+#: rhodecode/templates/admin/repos/repos.html:9
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
@@ -305,547 +399,929 @@ msgstr "Gravar"
 #: rhodecode/templates/admin/settings/settings.html:9
 #: rhodecode/templates/admin/users/user_add.html:8
 #: rhodecode/templates/admin/users/user_edit.html:9
-#: rhodecode/templates/admin/users/user_edit.html:110
+#: rhodecode/templates/admin/users/user_edit.html:122
 #: rhodecode/templates/admin/users/users.html:9
 #: rhodecode/templates/admin/users_groups/users_group_add.html:8
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:9
 #: rhodecode/templates/admin/users_groups/users_groups.html:9
-#: rhodecode/templates/base/base.html:279
-#: rhodecode/templates/base/base.html:366
-#: rhodecode/templates/base/base.html:368
-#: rhodecode/templates/base/base.html:370
+#: rhodecode/templates/base/base.html:197
+#: rhodecode/templates/base/base.html:337
+#: rhodecode/templates/base/base.html:339
+#: rhodecode/templates/base/base.html:341
 msgid "Admin"
 msgstr "Administrador"
 
-#: rhodecode/controllers/admin/permissions.py:62
+#: rhodecode/controllers/admin/permissions.py:65
 msgid "disabled"
 msgstr "desabilitado"
 
-#: rhodecode/controllers/admin/permissions.py:64
+#: rhodecode/controllers/admin/permissions.py:67
 msgid "allowed with manual account activation"
 msgstr "permitido com ativação manual de conta"
 
-#: rhodecode/controllers/admin/permissions.py:66
+#: rhodecode/controllers/admin/permissions.py:69
 msgid "allowed with automatic account activation"
 msgstr "permitido com ativação automática de conta"
 
-#: rhodecode/controllers/admin/permissions.py:68
+#: rhodecode/controllers/admin/permissions.py:71
+#: rhodecode/controllers/admin/permissions.py:74
 msgid "Disabled"
 msgstr "Desabilitado"
 
-#: rhodecode/controllers/admin/permissions.py:69
+#: rhodecode/controllers/admin/permissions.py:72
+#: rhodecode/controllers/admin/permissions.py:75
 msgid "Enabled"
 msgstr "Habilitado"
 
-#: rhodecode/controllers/admin/permissions.py:102
+#: rhodecode/controllers/admin/permissions.py:116
 msgid "Default permissions updated successfully"
 msgstr "Permissões padrões atualizadas com sucesso"
 
-#: rhodecode/controllers/admin/permissions.py:119
+#: rhodecode/controllers/admin/permissions.py:130
 msgid "error occurred during update of permissions"
 msgstr "ocorreu um erro ao atualizar as permissões"
 
-#: rhodecode/controllers/admin/repos.py:96
-#, 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 "repositório %s não está mapeado ao bd. Talvez ele tenha sido criado ou renomeado a partir do sistema de arquivos. Por favor execute a aplicação outra vez para varrer novamente por repositórios"
-
-#: rhodecode/controllers/admin/repos.py:172
+#: rhodecode/controllers/admin/repos.py:123
+msgid "--REMOVE FORK--"
+msgstr "--REMOVER BIFURCAÇÂO--"
+
+#: rhodecode/controllers/admin/repos.py:192
 #, python-format
 msgid "created repository %s from %s"
 msgstr "repositório %s criado a partir de %s"
 
-#: rhodecode/controllers/admin/repos.py:176
+#: rhodecode/controllers/admin/repos.py:196
 #, python-format
 msgid "created repository %s"
 msgstr "repositório %s criado"
 
-#: rhodecode/controllers/admin/repos.py:205
+#: rhodecode/controllers/admin/repos.py:227
 #, python-format
 msgid "error occurred during creation of repository %s"
 msgstr "ocorreu um erro ao criar o repositório %s"
 
-#: rhodecode/controllers/admin/repos.py:292
+#: rhodecode/controllers/admin/repos.py:319
 #, python-format
 msgid "Cannot delete %s it still contains attached forks"
 msgstr "Nao é possível excluir %s pois ele ainda contém bifurcações vinculadas"
 
-#: rhodecode/controllers/admin/repos.py:320
+#: rhodecode/controllers/admin/repos.py:348
 msgid "An error occurred during deletion of repository user"
 msgstr "Ocorreu um erro ao excluir usuário de repositório"
 
-#: rhodecode/controllers/admin/repos.py:335
+#: rhodecode/controllers/admin/repos.py:367
 msgid "An error occurred during deletion of repository users groups"
 msgstr "Ocorreu um erro ao excluir grupo de usuário de repositório"
 
-#: rhodecode/controllers/admin/repos.py:352
+#: rhodecode/controllers/admin/repos.py:385
 msgid "An error occurred during deletion of repository stats"
 msgstr "Ocorreu um erro ao excluir estatísticas de repositório"
 
-#: rhodecode/controllers/admin/repos.py:367
+#: rhodecode/controllers/admin/repos.py:402
 msgid "An error occurred during cache invalidation"
 msgstr "Ocorreu um erro ao invalidar o cache"
 
-#: rhodecode/controllers/admin/repos.py:387
+#: rhodecode/controllers/admin/repos.py:422
+#, fuzzy
+msgid "An error occurred during unlocking"
+msgstr "Ocorreu um erro durante essa operação"
+
+#: rhodecode/controllers/admin/repos.py:442
 msgid "Updated repository visibility in public journal"
 msgstr "Atualizada a visibilidade do repositório no diário público"
 
-#: rhodecode/controllers/admin/repos.py:390
+#: rhodecode/controllers/admin/repos.py:446
 msgid "An error occurred during setting this repository in public journal"
 msgstr "Ocorreu um erro ao ajustar esse repositório no diário público"
 
-#: rhodecode/controllers/admin/repos.py:395
-#: rhodecode/model/forms.py:53
+#: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
 msgid "Token mismatch"
 msgstr "Descompasso de Token"
 
-#: rhodecode/controllers/admin/repos.py:408
+#: rhodecode/controllers/admin/repos.py:464
 msgid "Pulled from remote location"
 msgstr "Realizado pull de localização remota"
 
-#: rhodecode/controllers/admin/repos.py:410
+#: rhodecode/controllers/admin/repos.py:466
 msgid "An error occurred during pull from remote location"
 msgstr "Ocorreu um erro ao realizar pull de localização remota"
 
-#: rhodecode/controllers/admin/repos_groups.py:83
+#: rhodecode/controllers/admin/repos.py:482
+msgid "Nothing"
+msgstr "Nada"
+
+#: rhodecode/controllers/admin/repos.py:484
+#, python-format
+msgid "Marked repo %s as fork of %s"
+msgstr "Marcado repositório %s como bifurcação de %s"
+
+#: rhodecode/controllers/admin/repos.py:488
+msgid "An error occurred during this operation"
+msgstr "Ocorreu um erro durante essa operação"
+
+#: rhodecode/controllers/admin/repos_groups.py:116
 #, python-format
 msgid "created repos group %s"
 msgstr "criado grupo de repositórios %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:96
+#: rhodecode/controllers/admin/repos_groups.py:129
 #, python-format
 msgid "error occurred during creation of repos group %s"
 msgstr "ccorreu um erro ao criar grupo de repositório %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:130
+#: rhodecode/controllers/admin/repos_groups.py:163
 #, python-format
 msgid "updated repos group %s"
 msgstr "atualizado grupo de repositórios %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:143
+#: rhodecode/controllers/admin/repos_groups.py:176
 #, python-format
 msgid "error occurred during update of repos group %s"
 msgstr "ocorreu um erro ao atualizar grupo de repositórios %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:164
+#: rhodecode/controllers/admin/repos_groups.py:194
 #, python-format
 msgid "This group contains %s repositores and cannot be deleted"
 msgstr "Esse grupo contém %s repositórios e não pode ser excluído"
 
-#: rhodecode/controllers/admin/repos_groups.py:171
+#: rhodecode/controllers/admin/repos_groups.py:202
 #, python-format
 msgid "removed repos group %s"
 msgstr "removido grupo de repositórios %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:175
+#: rhodecode/controllers/admin/repos_groups.py:208
+msgid "Cannot delete this group it still contains subgroups"
+msgstr "Nao é possível excluir este grupo pois ele ainda contém subgrupos"
+
+#: rhodecode/controllers/admin/repos_groups.py:213
+#: rhodecode/controllers/admin/repos_groups.py:218
 #, python-format
 msgid "error occurred during deletion of repos group %s"
 msgstr "ccorreu um erro ao excluir grupo de repositórios %s"
 
-#: rhodecode/controllers/admin/settings.py:109
+#: rhodecode/controllers/admin/repos_groups.py:238
+msgid "An error occurred during deletion of group user"
+msgstr "Ocorreu um erro ao excluir o usuário de grupo"
+
+#: rhodecode/controllers/admin/repos_groups.py:258
+msgid "An error occurred during deletion of group users groups"
+msgstr "Ocorreu um erro ao excluir o grupo do grupo de usuários"
+
+#: rhodecode/controllers/admin/settings.py:121
 #, python-format
 msgid "Repositories successfully rescanned added: %s,removed: %s"
 msgstr "Repositórios varridos com sucesso adicionados: %s, removidos: %s"
 
-#: rhodecode/controllers/admin/settings.py:118
+#: rhodecode/controllers/admin/settings.py:129
 msgid "Whoosh reindex task scheduled"
 msgstr "Tarefa de reindexação do whoosh agendada"
 
-#: rhodecode/controllers/admin/settings.py:143
+#: rhodecode/controllers/admin/settings.py:160
 msgid "Updated application settings"
 msgstr "Configurações da aplicação atualizadas"
 
-#: rhodecode/controllers/admin/settings.py:148
-#: rhodecode/controllers/admin/settings.py:215
+#: rhodecode/controllers/admin/settings.py:164
+#: rhodecode/controllers/admin/settings.py:275
 msgid "error occurred during updating application settings"
 msgstr "ocorreu um erro ao atualizar as configurações da aplicação"
 
-#: rhodecode/controllers/admin/settings.py:210
-msgid "Updated mercurial settings"
+#: rhodecode/controllers/admin/settings.py:200
+#, fuzzy
+msgid "Updated visualisation settings"
+msgstr "Configurações da aplicação atualizadas"
+
+#: rhodecode/controllers/admin/settings.py:205
+#, fuzzy
+msgid "error occurred during updating visualisation settings"
+msgstr "ocorreu um erro ao atualizar as configurações da aplicação"
+
+#: rhodecode/controllers/admin/settings.py:271
+#, fuzzy
+msgid "Updated VCS settings"
 msgstr "Atualizadas as configurações do mercurial"
 
-#: rhodecode/controllers/admin/settings.py:236
+#: rhodecode/controllers/admin/settings.py:285
 msgid "Added new hook"
 msgstr "Adicionado novo gancho"
 
-#: rhodecode/controllers/admin/settings.py:247
+#: rhodecode/controllers/admin/settings.py:297
 msgid "Updated hooks"
 msgstr "Atualizados os ganchos"
 
-#: rhodecode/controllers/admin/settings.py:251
+#: rhodecode/controllers/admin/settings.py:301
 msgid "error occurred during hook creation"
 msgstr "ocorreu um erro ao criar gancho"
 
-#: rhodecode/controllers/admin/settings.py:310
+#: rhodecode/controllers/admin/settings.py:320
+msgid "Email task created"
+msgstr "Tarefa de e-mail criada"
+
+#: rhodecode/controllers/admin/settings.py:375
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr "Você não pode editar esse usuário pois ele é crucial para toda a aplicação"
 
-#: rhodecode/controllers/admin/settings.py:339
+#: rhodecode/controllers/admin/settings.py:406
 msgid "Your account was updated successfully"
 msgstr "Sua conta foi atualizada com sucesso"
 
-#: rhodecode/controllers/admin/settings.py:359
-#: rhodecode/controllers/admin/users.py:130
+#: rhodecode/controllers/admin/settings.py:421
+#: rhodecode/controllers/admin/users.py:191
 #, python-format
 msgid "error occurred during update of user %s"
 msgstr "ocorreu um erro ao atualizar o usuário %s"
 
-#: rhodecode/controllers/admin/users.py:78
+#: rhodecode/controllers/admin/users.py:130
 #, python-format
 msgid "created user %s"
 msgstr "usuário %s criado"
 
-#: rhodecode/controllers/admin/users.py:90
+#: rhodecode/controllers/admin/users.py:142
 #, python-format
 msgid "error occurred during creation of user %s"
 msgstr "ocorreu um erro ao criar o usuário %s"
 
-#: rhodecode/controllers/admin/users.py:116
+#: rhodecode/controllers/admin/users.py:171
 msgid "User updated successfully"
 msgstr "Usuário atualizado com sucesso"
 
-#: rhodecode/controllers/admin/users.py:146
+#: rhodecode/controllers/admin/users.py:207
 msgid "successfully deleted user"
 msgstr "usuário excluído com sucesso"
 
-#: rhodecode/controllers/admin/users.py:150
+#: rhodecode/controllers/admin/users.py:212
 msgid "An error occurred during deletion of user"
 msgstr "Ocorreu um erro ao excluir o usuário"
 
-#: rhodecode/controllers/admin/users.py:166
+#: rhodecode/controllers/admin/users.py:226
 msgid "You can't edit this user"
 msgstr "Você não pode editar esse usuário"
 
-#: rhodecode/controllers/admin/users.py:195
-#: rhodecode/controllers/admin/users_groups.py:202
+#: rhodecode/controllers/admin/users.py:266
 msgid "Granted 'repository create' permission to user"
 msgstr "Concedida permissão de 'criar repositório' ao usuário"
 
-#: rhodecode/controllers/admin/users.py:204
-#: rhodecode/controllers/admin/users_groups.py:211
+#: rhodecode/controllers/admin/users.py:271
 msgid "Revoked 'repository create' permission to user"
 msgstr "Revogada permissão de 'criar repositório' ao usuário"
 
-#: rhodecode/controllers/admin/users_groups.py:74
+#: rhodecode/controllers/admin/users.py:277
+#, fuzzy
+msgid "Granted 'repository fork' permission to user"
+msgstr "Concedida permissão de 'criar repositório' ao usuário"
+
+#: rhodecode/controllers/admin/users.py:282
+#, fuzzy
+msgid "Revoked 'repository fork' permission to user"
+msgstr "Revogada permissão de 'criar repositório' ao usuário"
+
+#: rhodecode/controllers/admin/users.py:288
+#: rhodecode/controllers/admin/users_groups.py:255
+#, fuzzy
+msgid "An error occurred during permissions saving"
+msgstr "Ocorreu um erro durante essa operação"
+
+#: rhodecode/controllers/admin/users.py:303
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:309
+#, fuzzy
+msgid "An error occurred during email saving"
+msgstr "Ocorreu um erro durante essa operação"
+
+#: rhodecode/controllers/admin/users.py:319
+#, fuzzy
+msgid "Removed email from user"
+msgstr "removido grupo de repositórios %s"
+
+#: rhodecode/controllers/admin/users_groups.py:84
 #, python-format
 msgid "created users group %s"
 msgstr "criado grupo de usuários %s"
 
-#: rhodecode/controllers/admin/users_groups.py:86
+#: rhodecode/controllers/admin/users_groups.py:95
 #, python-format
 msgid "error occurred during creation of users group %s"
 msgstr "ocorreu um erro ao criar o grupo de usuários %s"
 
-#: rhodecode/controllers/admin/users_groups.py:119
+#: rhodecode/controllers/admin/users_groups.py:135
 #, python-format
 msgid "updated users group %s"
 msgstr "grupo de usuários %s atualizado"
 
-#: rhodecode/controllers/admin/users_groups.py:138
+#: rhodecode/controllers/admin/users_groups.py:157
 #, python-format
 msgid "error occurred during update of users group %s"
 msgstr "ocorreu um erro ao atualizar o grupo de usuários %s"
 
-#: rhodecode/controllers/admin/users_groups.py:154
+#: rhodecode/controllers/admin/users_groups.py:174
 msgid "successfully deleted users group"
 msgstr "grupo de usuários excluído com sucesso"
 
-#: rhodecode/controllers/admin/users_groups.py:158
+#: rhodecode/controllers/admin/users_groups.py:179
 msgid "An error occurred during deletion of users group"
 msgstr "Ocorreu um erro ao excluir o grupo de usuários"
 
-#: rhodecode/lib/__init__.py:279
-msgid "year"
-msgstr "ano"
-
-#: rhodecode/lib/__init__.py:280
-msgid "month"
-msgstr "mês"
-
-#: rhodecode/lib/__init__.py:281
-msgid "day"
-msgstr "dia"
-
-#: rhodecode/lib/__init__.py:282
-msgid "hour"
-msgstr "hora"
-
-#: rhodecode/lib/__init__.py:283
-msgid "minute"
-msgstr "minuto"
-
-#: rhodecode/lib/__init__.py:284
-msgid "second"
-msgstr "segundo"
-
-#: rhodecode/lib/__init__.py:293
-msgid "ago"
-msgstr "atrás"
-
-#: rhodecode/lib/__init__.py:296
-msgid "just now"
-msgstr "agora há pouco"
-
-#: rhodecode/lib/auth.py:377
+#: rhodecode/controllers/admin/users_groups.py:233
+#, fuzzy
+msgid "Granted 'repository create' permission to users group"
+msgstr "Concedida permissão de 'criar repositório' ao usuário"
+
+#: rhodecode/controllers/admin/users_groups.py:238
+#, fuzzy
+msgid "Revoked 'repository create' permission to users group"
+msgstr "Revogada permissão de 'criar repositório' ao usuário"
+
+#: rhodecode/controllers/admin/users_groups.py:244
+#, fuzzy
+msgid "Granted 'repository fork' permission to users group"
+msgstr "Concedida permissão de 'criar repositório' ao usuário"
+
+#: rhodecode/controllers/admin/users_groups.py:249
+#, fuzzy
+msgid "Revoked 'repository fork' permission to users group"
+msgstr "Revogada permissão de 'criar repositório' ao usuário"
+
+#: rhodecode/lib/auth.py:499
 msgid "You need to be a registered user to perform this action"
 msgstr "Você precisa ser um usuário registrado para realizar essa ação"
 
-#: rhodecode/lib/auth.py:421
+#: rhodecode/lib/auth.py:540
 msgid "You need to be a signed in to view this page"
 msgstr "Você precisa estar logado para ver essa página"
 
-#: rhodecode/lib/helpers.py:307
+#: rhodecode/lib/diffs.py:86
+msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+"Conjunto de mudanças é grande demais e foi cortado, use o menu de "
+"diferenças para ver as diferenças"
+
+#: rhodecode/lib/diffs.py:96
+msgid "No changes detected"
+msgstr "Nenhuma alteração detectada"
+
+#: rhodecode/lib/helpers.py:372
+#, python-format
+msgid "%a, %d %b %Y %H:%M:%S"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:484
 msgid "True"
 msgstr "Verdadeiro"
 
-#: rhodecode/lib/helpers.py:311
+#: rhodecode/lib/helpers.py:488
 msgid "False"
 msgstr "Falso"
 
-#: rhodecode/lib/helpers.py:352
+#: rhodecode/lib/helpers.py:532
+msgid "Changeset not found"
+msgstr "Conjunto de alterações não encontrado"
+
+#: rhodecode/lib/helpers.py:555
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr "Ver todos os conjuntos de mudanças combinados %s->%s"
 
-#: rhodecode/lib/helpers.py:356
+#: rhodecode/lib/helpers.py:561
 msgid "compare view"
 msgstr "comparar exibir"
 
-#: rhodecode/lib/helpers.py:365
+#: rhodecode/lib/helpers.py:581
 msgid "and"
 msgstr "e"
 
-#: rhodecode/lib/helpers.py:365
+#: rhodecode/lib/helpers.py:582
 #, python-format
 msgid "%s more"
 msgstr "%s mais"
 
-#: rhodecode/lib/helpers.py:367
-#: rhodecode/templates/changelog/changelog.html:14
-#: rhodecode/templates/changelog/changelog.html:39
+#: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
 msgid "revisions"
 msgstr "revisões"
 
-#: rhodecode/lib/helpers.py:385
+#: rhodecode/lib/helpers.py:606
 msgid "fork name "
 msgstr "nome da bifurcação"
 
-#: rhodecode/lib/helpers.py:388
+#: rhodecode/lib/helpers.py:620
+#: rhodecode/templates/pullrequests/pullrequest_show.html:4
+#: rhodecode/templates/pullrequests/pullrequest_show.html:12
+#, python-format
+msgid "Pull request #%s"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:626
 msgid "[deleted] repository"
 msgstr "repositório [excluído]"
 
-#: rhodecode/lib/helpers.py:389
-#: rhodecode/lib/helpers.py:393
+#: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
 msgid "[created] repository"
 msgstr "repositório [criado]"
 
-#: rhodecode/lib/helpers.py:390
-#: rhodecode/lib/helpers.py:394
+#: rhodecode/lib/helpers.py:630
+msgid "[created] repository as fork"
+msgstr "repositório [criado] como uma bifurcação"
+
+#: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
 msgid "[forked] repository"
 msgstr "repositório [bifurcado]"
 
-#: rhodecode/lib/helpers.py:391
-#: rhodecode/lib/helpers.py:395
+#: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
 msgid "[updated] repository"
 msgstr "repositório [atualizado]"
 
-#: rhodecode/lib/helpers.py:392
+#: rhodecode/lib/helpers.py:636
 msgid "[delete] repository"
 msgstr "[excluir] repositório"
 
-#: rhodecode/lib/helpers.py:396
+#: rhodecode/lib/helpers.py:644
+#, fuzzy
+msgid "[created] user"
+msgstr "usuário %s criado"
+
+#: rhodecode/lib/helpers.py:646
+#, fuzzy
+msgid "[updated] user"
+msgstr "grupo de usuários %s atualizado"
+
+#: rhodecode/lib/helpers.py:648
+#, fuzzy
+msgid "[created] users group"
+msgstr "criado grupo de usuários %s"
+
+#: rhodecode/lib/helpers.py:650
+#, fuzzy
+msgid "[updated] users group"
+msgstr "grupo de usuários %s atualizado"
+
+#: rhodecode/lib/helpers.py:652
+#, fuzzy
+msgid "[commented] on revision in repository"
+msgstr "repositório [criado]"
+
+#: rhodecode/lib/helpers.py:654
+#, fuzzy
+msgid "[commented] on pull request for"
+msgstr "repositório [criado]"
+
+#: rhodecode/lib/helpers.py:656
+#, fuzzy
+msgid "[closed] pull request for"
+msgstr "repositório [criado]"
+
+#: rhodecode/lib/helpers.py:658
 msgid "[pushed] into"
 msgstr "[realizado push] para"
 
-#: rhodecode/lib/helpers.py:397
-msgid "[committed via RhodeCode] into"
+#: rhodecode/lib/helpers.py:660
+#, fuzzy
+msgid "[committed via RhodeCode] into repository"
 msgstr "[realizado commit via RhodeCode] para"
 
-#: rhodecode/lib/helpers.py:398
-msgid "[pulled from remote] into"
+#: rhodecode/lib/helpers.py:662
+#, fuzzy
+msgid "[pulled from remote] into repository"
 msgstr "[realizado pull remoto] para"
 
-#: rhodecode/lib/helpers.py:399
+#: rhodecode/lib/helpers.py:664
 msgid "[pulled] from"
 msgstr "[realizado pull] a partir de"
 
-#: rhodecode/lib/helpers.py:400
+#: rhodecode/lib/helpers.py:666
 msgid "[started following] repository"
 msgstr "[passou a seguir] o repositório"
 
-#: rhodecode/lib/helpers.py:401
+#: rhodecode/lib/helpers.py:668
 msgid "[stopped following] repository"
 msgstr "[parou de seguir] o repositório"
 
-#: rhodecode/lib/helpers.py:577
+#: rhodecode/lib/helpers.py:840
 #, python-format
 msgid " and %s more"
 msgstr " e mais %s"
 
-#: rhodecode/lib/helpers.py:581
+#: rhodecode/lib/helpers.py:844
 msgid "No Files"
 msgstr "Nenhum Arquivo"
 
-#: rhodecode/model/forms.py:66
-msgid "Invalid username"
-msgstr "Nome de usuário inválido"
-
-#: rhodecode/model/forms.py:75
-msgid "This username already exists"
-msgstr "Esse nome de usuário já existe"
-
-#: rhodecode/model/forms.py:79
-msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
-msgstr "Nome de usuário pode conter somente caracteres alfanuméricos, sublinha, pontos e hífens e deve iniciar com caractere alfanumérico"
-
-#: rhodecode/model/forms.py:94
-msgid "Invalid group name"
-msgstr "Nome de grupo inválido"
-
-#: rhodecode/model/forms.py:104
-msgid "This users group already exists"
-msgstr "Esse grupo de usuários já existe"
-
-#: rhodecode/model/forms.py:110
-msgid "Group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
-msgstr "Nome de grupo pode conter somente caracteres alfanuméricos, sublinha, pontos e hífens e deve iniciar com caractere alfanumérico"
-
-#: rhodecode/model/forms.py:132
-msgid "Cannot assign this group as parent"
-msgstr "Não é possível associar esse grupo como progenitor"
-
-#: rhodecode/model/forms.py:148
-msgid "This group already exists"
-msgstr "Esse grupo já existe"
-
-#: rhodecode/model/forms.py:164
-#: rhodecode/model/forms.py:172
-#: rhodecode/model/forms.py:180
-msgid "Invalid characters in password"
-msgstr "Caracteres inválidos na senha"
-
-#: rhodecode/model/forms.py:191
-msgid "Passwords do not match"
-msgstr "Senhas não conferem"
-
-#: rhodecode/model/forms.py:196
-msgid "invalid password"
-msgstr "senha inválida"
-
-#: rhodecode/model/forms.py:197
-msgid "invalid user name"
-msgstr "nome de usuário inválido"
-
-#: rhodecode/model/forms.py:198
-msgid "Your account is disabled"
-msgstr "Sua conta está desabilitada"
-
-#: rhodecode/model/forms.py:233
-msgid "This username is not valid"
-msgstr "Esse nome de usuário não é válido"
-
-#: rhodecode/model/forms.py:245
-msgid "This repository name is disallowed"
-msgstr "Esse nome de repositório não é permitido"
-
-#: rhodecode/model/forms.py:266
+#: rhodecode/lib/utils2.py:335
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "%d ano"
+msgstr[1] "%d anos"
+
+#: rhodecode/lib/utils2.py:336
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "%d mês"
+msgstr[1] "%d meses"
+
+#: rhodecode/lib/utils2.py:337
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "%d dia"
+msgstr[1] "%d dias"
+
+#: rhodecode/lib/utils2.py:338
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "%d hora"
+msgstr[1] "%d horas"
+
+#: rhodecode/lib/utils2.py:339
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "%d minuto"
+msgstr[1] "%d minutos"
+
+#: rhodecode/lib/utils2.py:340
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] "%d segundo"
+msgstr[1] "%d segundos"
+
+#: rhodecode/lib/utils2.py:355
+#, python-format
+msgid "%s ago"
+msgstr "%s atrás"
+
+#: rhodecode/lib/utils2.py:357
+#, python-format
+msgid "%s and %s ago"
+msgstr "%s e %s atrás"
+
+#: rhodecode/lib/utils2.py:360
+msgid "just now"
+msgstr "agora há pouco"
+
+#: rhodecode/lib/celerylib/tasks.py:269
+msgid "password reset link"
+msgstr "link de reinicialização de senha"
+
+#: rhodecode/model/comment.py:110
 #, python-format
-msgid "This repository already exists in group \"%s\""
-msgstr "Esse repositório já existe no grupo \"%s\""
-
-#: rhodecode/model/forms.py:274
-msgid "This repository already exists"
+msgid "on line %s"
+msgstr "na linha %s"
+
+#: rhodecode/model/comment.py:157
+msgid "[Mention]"
+msgstr "[Menção]"
+
+#: rhodecode/model/db.py:1140
+#, fuzzy
+msgid "Repository no access"
+msgstr "repositórios"
+
+#: rhodecode/model/db.py:1141
+#, fuzzy
+msgid "Repository read access"
 msgstr "Esse repositório já existe"
 
-#: rhodecode/model/forms.py:312
-#: rhodecode/model/forms.py:319
-msgid "invalid clone url"
-msgstr "URL de clonagem inválida"
-
-#: rhodecode/model/forms.py:322
-msgid "Invalid clone url, provide a valid clone http\\s url"
-msgstr "URL de clonagem inválida, forneça uma URL válida de clonagem http\\s"
-
-#: rhodecode/model/forms.py:334
-msgid "Fork have to be the same type as original"
-msgstr "Bifurcação precisa ser do mesmo tipo que o original"
-
-#: rhodecode/model/forms.py:341
-msgid "This username or users group name is not valid"
-msgstr "Esse nome de usuário ou nome de grupo de usuários não é válido"
-
-#: rhodecode/model/forms.py:403
-msgid "This is not a valid path"
-msgstr "Esse não é um caminho válido"
-
-#: rhodecode/model/forms.py:416
-msgid "This e-mail address is already taken"
-msgstr "Esse endereço de e-mail já está tomado"
-
-#: rhodecode/model/forms.py:427
-msgid "This e-mail address doesn't exist."
-msgstr "Esse endereço de e-mail não existe."
-
-#: rhodecode/model/forms.py:447
-msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to 'username'"
-msgstr "O atributo de login LDAP do CN deve ser especificado - isto é o nome do atributo que é equivalente ao 'nome de usuário'"
-
-#: rhodecode/model/forms.py:466
+#: rhodecode/model/db.py:1142
+#, fuzzy
+msgid "Repository write access"
+msgstr "repositórios"
+
+#: rhodecode/model/db.py:1143
+#, fuzzy
+msgid "Repository admin access"
+msgstr "repositórios"
+
+#: rhodecode/model/db.py:1145
+#, fuzzy
+msgid "Repositories Group no access"
+msgstr "grupos de repositórios"
+
+#: rhodecode/model/db.py:1146
+#, fuzzy
+msgid "Repositories Group read access"
+msgstr "grupos de repositórios"
+
+#: rhodecode/model/db.py:1147
+#, fuzzy
+msgid "Repositories Group write access"
+msgstr "grupos de repositórios"
+
+#: rhodecode/model/db.py:1148
+#, fuzzy
+msgid "Repositories Group admin access"
+msgstr "grupos de repositórios"
+
+#: rhodecode/model/db.py:1150
+#, fuzzy
+msgid "RhodeCode Administrator"
+msgstr "Administração de usuários"
+
+#: rhodecode/model/db.py:1151
+#, fuzzy
+msgid "Repository creation disabled"
+msgstr "Criação de repositório"
+
+#: rhodecode/model/db.py:1152
+#, fuzzy
+msgid "Repository creation enabled"
+msgstr "Criação de repositório"
+
+#: rhodecode/model/db.py:1153
+#, fuzzy
+msgid "Repository forking disabled"
+msgstr "Criação de repositório"
+
+#: rhodecode/model/db.py:1154
+#, fuzzy
+msgid "Repository forking enabled"
+msgstr "Criação de repositório"
+
+#: rhodecode/model/db.py:1155
+#, fuzzy
+msgid "Register disabled"
+msgstr "desabilitado"
+
+#: rhodecode/model/db.py:1156
+msgid "Register new user with RhodeCode with manual activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1159
+msgid "Register new user with RhodeCode with auto activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1579
+msgid "Not Reviewed"
+msgstr ""
+
+#: rhodecode/model/db.py:1580
+#, fuzzy
+msgid "Approved"
+msgstr "removidos"
+
+#: rhodecode/model/db.py:1581
+msgid "Rejected"
+msgstr ""
+
+#: rhodecode/model/db.py:1582
+msgid "Under Review"
+msgstr ""
+
+#: rhodecode/model/forms.py:43
 msgid "Please enter a login"
 msgstr "Por favor entre um login"
 
-#: rhodecode/model/forms.py:467
+#: rhodecode/model/forms.py:44
 #, python-format
 msgid "Enter a value %(min)i characters long or more"
 msgstr "Entre um valor com %(min)i caracteres ou mais"
 
-#: rhodecode/model/forms.py:475
+#: rhodecode/model/forms.py:52
 msgid "Please enter a password"
 msgstr "Por favor entre com uma senha"
 
-#: rhodecode/model/forms.py:476
+#: rhodecode/model/forms.py:53
 #, python-format
 msgid "Enter %(min)i characters or more"
 msgstr "Entre com %(min)i caracteres ou mais"
 
-#: rhodecode/model/user.py:145
-msgid "[RhodeCode] New User registration"
-msgstr "[RhodeCode] Registro de Novo Usuário"
-
-#: rhodecode/model/user.py:157
-#: rhodecode/model/user.py:179
+#: rhodecode/model/notification.py:220
+msgid "commented on commit"
+msgstr "comentado no commit"
+
+#: rhodecode/model/notification.py:221
+msgid "sent message"
+msgstr "mensagem enviada"
+
+#: rhodecode/model/notification.py:222
+msgid "mentioned you"
+msgstr "mencionou você"
+
+#: rhodecode/model/notification.py:223
+msgid "registered in RhodeCode"
+msgstr "registrado no RhodeCode"
+
+#: rhodecode/model/notification.py:224
+msgid "opened new pull request"
+msgstr ""
+
+#: rhodecode/model/notification.py:225
+#, fuzzy
+msgid "commented on pull request"
+msgstr "comentado no commit"
+
+#: rhodecode/model/pull_request.py:84
+#, python-format
+msgid "%(user)s wants you to review pull request #%(pr_id)s"
+msgstr ""
+
+#: rhodecode/model/scm.py:535
+#, fuzzy
+msgid "latest tip"
+msgstr "último login"
+
+#: rhodecode/model/user.py:230
+msgid "new user registration"
+msgstr "registro de novo usuário"
+
+#: rhodecode/model/user.py:255 rhodecode/model/user.py:277
+#: rhodecode/model/user.py:299
 msgid "You can't Edit this user since it's crucial for entire application"
-msgstr "Você não pode Editar esse usuário, pois ele é crucial para toda a aplicação"
-
-#: rhodecode/model/user.py:201
+msgstr ""
+"Você não pode Editar esse usuário, pois ele é crucial para toda a "
+"aplicação"
+
+#: rhodecode/model/user.py:323
 msgid "You can't remove this user since it's crucial for entire application"
-msgstr "Você não pode remover esse usuário, pois ele é crucial para toda a aplicação"
-
-#: rhodecode/model/user.py:204
+msgstr ""
+"Você não pode remover esse usuário, pois ele é crucial para toda a "
+"aplicação"
+
+#: rhodecode/model/user.py:329
+#, python-format
+msgid ""
+"user \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories. %s"
+msgstr ""
+"usuário \"%s\" ainda é dono de %s repositórios e não pode ser removido. "
+"Troque os donos ou remova esses repositórios. %s"
+
+#: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: rhodecode/model/validators.py:82
+#, fuzzy, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr "Esse nome de usuário já existe"
+
+#: rhodecode/model/validators.py:84
 #, python-format
-msgid "This user still owns %s repositories and cannot be removed. Switch owners or remove those repositories"
-msgstr "Esse usuário ainda é dono de %s repositórios e não pode ser removido. Troque os donos ou remova esses repositórios"
-
-#: rhodecode/templates/index.html:4
+msgid "Username \"%(username)s\" is forbidden"
+msgstr ""
+
+#: rhodecode/model/validators.py:86
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or"
+" dashes and must begin with alphanumeric character"
+msgstr ""
+"Nome de usuário pode conter somente caracteres alfanuméricos, sublinha, "
+"pontos e hífens e deve iniciar com caractere alfanumérico"
+
+#: rhodecode/model/validators.py:114
+#, fuzzy, python-format
+msgid "Username %(username)s is not valid"
+msgstr "Esse nome de usuário ou nome de grupo de usuários não é válido"
+
+#: rhodecode/model/validators.py:133
+#, fuzzy
+msgid "Invalid users group name"
+msgstr "nome de usuário inválido"
+
+#: rhodecode/model/validators.py:134
+#, fuzzy, python-format
+msgid "Users group \"%(usersgroup)s\" already exists"
+msgstr "Esse grupo de usuários já existe"
+
+#: rhodecode/model/validators.py:136
+msgid ""
+"users group name may only contain  alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+"Nome de grupo de repositório pode conter somente caracteres "
+"alfanuméricos, sublinha, pontos e hífens e deve iniciar com caractere "
+"alfanumérico"
+
+#: rhodecode/model/validators.py:174
+msgid "Cannot assign this group as parent"
+msgstr "Não é possível associar esse grupo como progenitor"
+
+#: rhodecode/model/validators.py:175
+#, fuzzy, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr "Esse nome de usuário já existe"
+
+#: rhodecode/model/validators.py:177
+#, fuzzy, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr "Já existe um repositório com esse nome"
+
+#: rhodecode/model/validators.py:235
+#, fuzzy
+msgid "Invalid characters (non-ascii) in password"
+msgstr "Caracteres inválidos na senha"
+
+#: rhodecode/model/validators.py:250
+msgid "Passwords do not match"
+msgstr "Senhas não conferem"
+
+#: rhodecode/model/validators.py:267
+msgid "invalid password"
+msgstr "senha inválida"
+
+#: rhodecode/model/validators.py:268
+msgid "invalid user name"
+msgstr "nome de usuário inválido"
+
+#: rhodecode/model/validators.py:269
+msgid "Your account is disabled"
+msgstr "Sua conta está desabilitada"
+
+#: rhodecode/model/validators.py:313
+#, fuzzy, python-format
+msgid "Repository name %(repo)s is disallowed"
+msgstr "Esse nome de repositório não é permitido"
+
+#: rhodecode/model/validators.py:315
+#, fuzzy, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr "Já existe um repositório com esse nome"
+
+#: rhodecode/model/validators.py:316
+#, fuzzy, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr "Esse repositório já existe em um grupo \"%s\""
+
+#: rhodecode/model/validators.py:318
+#, fuzzy, python-format
+msgid "Repositories group with name \"%(repo)s\" already exists"
+msgstr "Já existe um repositório com esse nome"
+
+#: rhodecode/model/validators.py:431
+msgid "invalid clone url"
+msgstr "URL de clonagem inválida"
+
+#: rhodecode/model/validators.py:432
+#, fuzzy
+msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
+msgstr "URL de clonagem inválida, forneça uma URL válida de clonagem http\\s"
+
+#: rhodecode/model/validators.py:457
+#, fuzzy
+msgid "Fork have to be the same type as parent"
+msgstr "Bifurcação precisa ser do mesmo tipo que o original"
+
+#: rhodecode/model/validators.py:478
+msgid "This username or users group name is not valid"
+msgstr "Esse nome de usuário ou nome de grupo de usuários não é válido"
+
+#: rhodecode/model/validators.py:562
+msgid "This is not a valid path"
+msgstr "Esse não é um caminho válido"
+
+#: rhodecode/model/validators.py:577
+msgid "This e-mail address is already taken"
+msgstr "Esse endereço de e-mail já está tomado"
+
+#: rhodecode/model/validators.py:597
+#, fuzzy, python-format
+msgid "e-mail \"%(email)s\" does not exist."
+msgstr "Esse endereço de e-mail não existe."
+
+#: rhodecode/model/validators.py:634
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+"O atributo de login LDAP do CN deve ser especificado - isto é o nome do "
+"atributo que é equivalente ao 'nome de usuário'"
+
+#: rhodecode/model/validators.py:653
+#, python-format
+msgid "Revisions %(revs)s are already part of pull request or have set status"
+msgstr ""
+
+#: rhodecode/templates/index.html:3
 msgid "Dashboard"
 msgstr "Painel de Controle"
 
-#: rhodecode/templates/index_base.html:22
-#: rhodecode/templates/admin/users/user_edit_my_account.html:102
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/repo_switcher_list.html:4
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/users/user_edit_my_account.html:31
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/bookmarks/bookmarks.html:10
+#: rhodecode/templates/branches/branches.html:9
+#: rhodecode/templates/journal/journal.html:40
+#: rhodecode/templates/tags/tags.html:10
 msgid "quick filter..."
 msgstr "filtro rápido..."
 
-#: rhodecode/templates/index_base.html:23
-#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/base/base.html:221
 msgid "repositories"
 msgstr "repositórios"
 
+#: rhodecode/templates/index_base.html:13
+#: rhodecode/templates/index_base.html:15
+#: rhodecode/templates/admin/repos/repos.html:21
+msgid "ADD REPOSITORY"
+msgstr "ADICIONAR REPOSITÓRIO"
+
 #: rhodecode/templates/index_base.html:29
-#: rhodecode/templates/admin/repos/repos.html:22
-msgid "ADD NEW REPOSITORY"
-msgstr "ADICIONAR NOVO REPOSITÓRIO"
-
-#: rhodecode/templates/index_base.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
@@ -854,159 +1330,158 @@ msgstr "ADICIONAR NOVO REPOSITÓRIO"
 msgid "Group name"
 msgstr "Nome do grupo"
 
-#: rhodecode/templates/index_base.html:42
-#: rhodecode/templates/index_base.html:73
-#: rhodecode/templates/admin/repos/repo_add_base.html:44
-#: rhodecode/templates/admin/repos/repo_edit.html:64
-#: rhodecode/templates/admin/repos/repos.html:31
+#: rhodecode/templates/index_base.html:30
+#: rhodecode/templates/index_base.html:71
+#: rhodecode/templates/index_base.html:142
+#: rhodecode/templates/index_base.html:168
+#: rhodecode/templates/admin/repos/repo_add_base.html:56
+#: rhodecode/templates/admin/repos/repo_edit.html:75
+#: rhodecode/templates/admin/repos/repos.html:72
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
-#: rhodecode/templates/settings/repo_fork.html:40
-#: rhodecode/templates/settings/repo_settings.html:40
-#: rhodecode/templates/summary/summary.html:92
+#: rhodecode/templates/forks/fork.html:59
+#: rhodecode/templates/settings/repo_settings.html:66
+#: rhodecode/templates/summary/summary.html:105
 msgid "Description"
 msgstr "Descrição"
 
-#: rhodecode/templates/index_base.html:53
+#: rhodecode/templates/index_base.html:40
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
 msgid "Repositories group"
 msgstr "Grupo de repositórios"
 
-#: rhodecode/templates/index_base.html:72
+#: rhodecode/templates/index_base.html:70
+#: rhodecode/templates/index_base.html:166
 #: rhodecode/templates/admin/repos/repo_add_base.html:9
 #: rhodecode/templates/admin/repos/repo_edit.html:32
-#: rhodecode/templates/admin/repos/repos.html:30
-#: rhodecode/templates/admin/users/user_edit_my_account.html:117
-#: rhodecode/templates/files/files_browser.html:157
+#: rhodecode/templates/admin/repos/repos.html:70
+#: rhodecode/templates/admin/users/user_edit.html:192
+#: rhodecode/templates/admin/users/user_edit_my_account.html:59
+#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
+#: rhodecode/templates/bookmarks/bookmarks.html:36
+#: rhodecode/templates/bookmarks/bookmarks_data.html:6
+#: rhodecode/templates/branches/branches.html:51
+#: rhodecode/templates/files/files_browser.html:47
+#: rhodecode/templates/journal/journal.html:59
+#: rhodecode/templates/journal/journal.html:107
+#: rhodecode/templates/journal/journal.html:186
 #: rhodecode/templates/settings/repo_settings.html:31
-#: rhodecode/templates/summary/summary.html:31
-#: rhodecode/templates/summary/summary.html:107
+#: rhodecode/templates/summary/summary.html:43
+#: rhodecode/templates/summary/summary.html:123
+#: rhodecode/templates/tags/tags.html:36
+#: rhodecode/templates/tags/tags_data.html:6
 msgid "Name"
 msgstr "Nome"
 
-#: rhodecode/templates/index_base.html:74
-#: rhodecode/templates/admin/repos/repos.html:32
-#: rhodecode/templates/summary/summary.html:114
+#: rhodecode/templates/index_base.html:72
 msgid "Last change"
 msgstr "Última alteração"
 
+#: rhodecode/templates/index_base.html:73
+#: rhodecode/templates/index_base.html:171
+#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/journal/journal.html:188
+msgid "Tip"
+msgstr "Ponta"
+
+#: rhodecode/templates/index_base.html:74
+#: rhodecode/templates/index_base.html:173
+#: rhodecode/templates/admin/repos/repo_edit.html:121
+#: rhodecode/templates/admin/repos/repos.html:73
+msgid "Owner"
+msgstr "Dono"
+
 #: rhodecode/templates/index_base.html:75
-#: rhodecode/templates/admin/repos/repos.html:33
-msgid "Tip"
-msgstr "Ponta"
+#: rhodecode/templates/summary/summary.html:48
+#: rhodecode/templates/summary/summary.html:51
+msgid "RSS"
+msgstr "RSS"
 
 #: rhodecode/templates/index_base.html:76
-#: rhodecode/templates/admin/repos/repo_edit.html:97
-msgid "Owner"
-msgstr "Dono"
-
-#: rhodecode/templates/index_base.html:77
-#: rhodecode/templates/journal/public_journal.html:20
-#: rhodecode/templates/summary/summary.html:180
-#: rhodecode/templates/summary/summary.html:183
-msgid "RSS"
-msgstr "RSS"
-
-#: rhodecode/templates/index_base.html:78
-#: rhodecode/templates/journal/public_journal.html:23
-#: rhodecode/templates/summary/summary.html:181
-#: rhodecode/templates/summary/summary.html:184
 msgid "Atom"
 msgstr "Atom"
 
-#: rhodecode/templates/index_base.html:87
-#: rhodecode/templates/index_base.html:89
-#: rhodecode/templates/index_base.html:91
-#: rhodecode/templates/base/base.html:209
-#: rhodecode/templates/base/base.html:211
-#: rhodecode/templates/base/base.html:213
-#: rhodecode/templates/summary/summary.html:4
-msgid "Summary"
-msgstr "Sumário"
-
-#: rhodecode/templates/index_base.html:95
-#: rhodecode/templates/index_base.html:97
-#: rhodecode/templates/index_base.html:99
-#: rhodecode/templates/base/base.html:225
-#: rhodecode/templates/base/base.html:227
-#: rhodecode/templates/base/base.html:229
-#: rhodecode/templates/changelog/changelog.html:6
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "Changelog"
-msgstr "Registro de alterações"
-
-#: rhodecode/templates/index_base.html:103
-#: rhodecode/templates/index_base.html:105
-#: rhodecode/templates/index_base.html:107
-#: rhodecode/templates/base/base.html:268
-#: rhodecode/templates/base/base.html:270
-#: rhodecode/templates/base/base.html:272
-#: rhodecode/templates/files/files.html:4
-msgid "Files"
-msgstr "Arquivos"
-
-#: rhodecode/templates/index_base.html:116
-#: rhodecode/templates/admin/repos/repos.html:42
-#: rhodecode/templates/admin/users/user_edit_my_account.html:127
-#: rhodecode/templates/summary/summary.html:48
-msgid "Mercurial repository"
-msgstr "Repositório Mercurial"
-
-#: rhodecode/templates/index_base.html:118
-#: rhodecode/templates/admin/repos/repos.html:44
-#: rhodecode/templates/admin/users/user_edit_my_account.html:129
-#: rhodecode/templates/summary/summary.html:51
-msgid "Git repository"
-msgstr "Repositório Git"
-
-#: rhodecode/templates/index_base.html:123
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
-#: rhodecode/templates/journal/journal.html:53
-#: rhodecode/templates/summary/summary.html:56
-msgid "private repository"
-msgstr "repositório privado"
-
-#: rhodecode/templates/index_base.html:125
-#: rhodecode/templates/journal/journal.html:55
-#: rhodecode/templates/summary/summary.html:58
-msgid "public repository"
-msgstr "repositório público"
-
-#: rhodecode/templates/index_base.html:133
-#: rhodecode/templates/base/base.html:291
-#: rhodecode/templates/settings/repo_fork.html:13
-msgid "fork"
-msgstr "bifurcação"
-
-#: rhodecode/templates/index_base.html:134
-#: rhodecode/templates/admin/repos/repos.html:60
-#: rhodecode/templates/admin/users/user_edit_my_account.html:143
-#: rhodecode/templates/summary/summary.html:69
-#: rhodecode/templates/summary/summary.html:71
-msgid "Fork of"
-msgstr "Bifurcação de"
-
-#: rhodecode/templates/index_base.html:155
-#: rhodecode/templates/admin/repos/repos.html:73
-msgid "No changesets yet"
-msgstr "Ainda não há conjuntos de mudanças"
-
-#: rhodecode/templates/index_base.html:161
-#: rhodecode/templates/index_base.html:163
+#: rhodecode/templates/index_base.html:110
+#: rhodecode/templates/index_base.html:112
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "Assinar o feed rss de %s"
 
-#: rhodecode/templates/index_base.html:168
-#: rhodecode/templates/index_base.html:170
+#: rhodecode/templates/index_base.html:117
+#: rhodecode/templates/index_base.html:119
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "Assinar o feed atom de %s"
 
-#: rhodecode/templates/login.html:5
-#: rhodecode/templates/login.html:54
-#: rhodecode/templates/base/base.html:38
+#: rhodecode/templates/index_base.html:140
+msgid "Group Name"
+msgstr "Nome do Grupo"
+
+#: rhodecode/templates/index_base.html:158
+#: rhodecode/templates/index_base.html:198
+#: rhodecode/templates/admin/repos/repos.html:94
+#: rhodecode/templates/admin/users/user_edit_my_account.html:179
+#: rhodecode/templates/admin/users/users.html:107
+#: rhodecode/templates/bookmarks/bookmarks.html:60
+#: rhodecode/templates/branches/branches.html:77
+#: rhodecode/templates/journal/journal.html:211
+#: rhodecode/templates/tags/tags.html:60
+msgid "Click to sort ascending"
+msgstr "Clique para ordenar em ordem crescente"
+
+#: rhodecode/templates/index_base.html:159
+#: rhodecode/templates/index_base.html:199
+#: rhodecode/templates/admin/repos/repos.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account.html:180
+#: rhodecode/templates/admin/users/users.html:108
+#: rhodecode/templates/bookmarks/bookmarks.html:61
+#: rhodecode/templates/branches/branches.html:78
+#: rhodecode/templates/journal/journal.html:212
+#: rhodecode/templates/tags/tags.html:61
+msgid "Click to sort descending"
+msgstr "Clique para ordenar em ordem descrescente"
+
+#: rhodecode/templates/index_base.html:169
+msgid "Last Change"
+msgstr "Última Alteração"
+
+#: rhodecode/templates/index_base.html:200
+#: rhodecode/templates/admin/repos/repos.html:96
+#: rhodecode/templates/admin/users/user_edit_my_account.html:181
+#: rhodecode/templates/admin/users/users.html:109
+#: rhodecode/templates/bookmarks/bookmarks.html:62
+#: rhodecode/templates/branches/branches.html:79
+#: rhodecode/templates/journal/journal.html:213
+#: rhodecode/templates/tags/tags.html:62
+msgid "No records found."
+msgstr "Nenhum registro encontrado."
+
+#: rhodecode/templates/index_base.html:201
+#: rhodecode/templates/admin/repos/repos.html:97
+#: rhodecode/templates/admin/users/user_edit_my_account.html:182
+#: rhodecode/templates/admin/users/users.html:110
+#: rhodecode/templates/bookmarks/bookmarks.html:63
+#: rhodecode/templates/branches/branches.html:80
+#: rhodecode/templates/journal/journal.html:214
+#: rhodecode/templates/tags/tags.html:63
+msgid "Data error."
+msgstr "Erro de dados."
+
+#: rhodecode/templates/index_base.html:202
+#: rhodecode/templates/admin/repos/repos.html:98
+#: rhodecode/templates/admin/users/user_edit_my_account.html:183
+#: rhodecode/templates/admin/users/users.html:111
+#: rhodecode/templates/bookmarks/bookmarks.html:64
+#: rhodecode/templates/branches/branches.html:81
+#: rhodecode/templates/journal/journal.html:215
+#: rhodecode/templates/tags/tags.html:64
+msgid "Loading..."
+msgstr "Carregando..."
+
+#: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
 msgid "Sign In"
 msgstr "Entrar"
 
@@ -1014,31 +1489,32 @@ msgstr "Entrar"
 msgid "Sign In to"
 msgstr "Entrar em"
 
-#: 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:47
-#: rhodecode/templates/admin/users/user_edit_my_account.html:45
-#: rhodecode/templates/base/base.html:15
-#: rhodecode/templates/summary/summary.html:106
+#: rhodecode/templates/admin/users/user_edit.html:50
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
+#: rhodecode/templates/base/base.html:83
+#: rhodecode/templates/summary/summary.html:122
 msgid "Username"
 msgstr "Nome de usuário"
 
-#: 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:24
+#: rhodecode/templates/base/base.html:92
 msgid "Password"
 msgstr "Senha"
 
+#: rhodecode/templates/login.html:50
+msgid "Remember me"
+msgstr "Lembre-se de mim"
+
 #: rhodecode/templates/login.html:60
 msgid "Forgot your password ?"
 msgstr "Esqueceu sua senha ?"
 
-#: rhodecode/templates/login.html:63
-#: rhodecode/templates/base/base.html:35
+#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
 msgid "Don't have an account ?"
 msgstr "Não possui uma conta ?"
 
@@ -1060,10 +1536,11 @@ msgstr "Reinicializar minha senha"
 
 #: rhodecode/templates/password_reset.html:31
 msgid "Password reset link will be send to matching email address"
-msgstr "Link de reinicialização de senha será enviado ao endereço de e-mail correspondente"
-
-#: rhodecode/templates/register.html:5
-#: rhodecode/templates/register.html:74
+msgstr ""
+"Link de reinicialização de senha será enviado ao endereço de e-mail "
+"correspondente"
+
+#: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
 msgid "Sign Up"
 msgstr "Inscrever-se"
 
@@ -1076,24 +1553,24 @@ msgid "Re-enter password"
 msgstr "Repita a senha"
 
 #: rhodecode/templates/register.html:47
-#: rhodecode/templates/admin/users/user_add.html:50
-#: rhodecode/templates/admin/users/user_edit.html:74
-#: rhodecode/templates/admin/users/user_edit_my_account.html:63
+#: rhodecode/templates/admin/users/user_add.html:59
+#: rhodecode/templates/admin/users/user_edit.html:86
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
 msgid "First Name"
 msgstr "Primeiro Nome"
 
 #: rhodecode/templates/register.html:56
-#: rhodecode/templates/admin/users/user_add.html:59
-#: rhodecode/templates/admin/users/user_edit.html:83
-#: rhodecode/templates/admin/users/user_edit_my_account.html:72
+#: rhodecode/templates/admin/users/user_add.html:68
+#: rhodecode/templates/admin/users/user_edit.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
 msgid "Last Name"
 msgstr "Último Nome"
 
 #: rhodecode/templates/register.html:65
-#: rhodecode/templates/admin/users/user_add.html:68
-#: rhodecode/templates/admin/users/user_edit.html:92
-#: rhodecode/templates/admin/users/user_edit_my_account.html:81
-#: rhodecode/templates/summary/summary.html:108
+#: rhodecode/templates/admin/users/user_add.html:77
+#: rhodecode/templates/admin/users/user_edit.html:104
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
+#: rhodecode/templates/summary/summary.html:124
 msgid "Email"
 msgstr "E-mail"
 
@@ -1105,20 +1582,59 @@ msgstr "Sua conta será ativada logo após o registro ser concluído"
 msgid "Your account must wait for activation by administrator"
 msgstr "Sua conta precisa esperar ativação por um administrador"
 
-#: rhodecode/templates/repo_switcher_list.html:14
+#: rhodecode/templates/repo_switcher_list.html:11
+#: rhodecode/templates/admin/repos/repo_add_base.html:65
+#: rhodecode/templates/admin/repos/repo_edit.html:85
+#: rhodecode/templates/settings/repo_settings.html:76
 msgid "Private repository"
 msgstr "Repositório privado"
 
-#: rhodecode/templates/repo_switcher_list.html:19
+#: rhodecode/templates/repo_switcher_list.html:16
 msgid "Public repository"
 msgstr "Repositório público"
 
+#: rhodecode/templates/switch_to_list.html:3
+#: rhodecode/templates/branches/branches.html:14
+msgid "branches"
+msgstr "ramos"
+
+#: rhodecode/templates/switch_to_list.html:10
+#: rhodecode/templates/branches/branches_data.html:57
+msgid "There are no branches yet"
+msgstr "Ainda não há ramos"
+
+#: rhodecode/templates/switch_to_list.html:15
+#: rhodecode/templates/shortlog/shortlog_data.html:10
+#: rhodecode/templates/tags/tags.html:15
+msgid "tags"
+msgstr "etiquetas"
+
+#: rhodecode/templates/switch_to_list.html:22
+#: rhodecode/templates/tags/tags_data.html:33
+msgid "There are no tags yet"
+msgstr "Ainda não há etiquetas"
+
+#: rhodecode/templates/switch_to_list.html:28
+#: rhodecode/templates/bookmarks/bookmarks.html:15
+msgid "bookmarks"
+msgstr "marcadores"
+
+#: rhodecode/templates/switch_to_list.html:35
+#: rhodecode/templates/bookmarks/bookmarks_data.html:32
+msgid "There are no bookmarks yet"
+msgstr "Ainda não há marcadores"
+
 #: rhodecode/templates/admin/admin.html:5
 #: rhodecode/templates/admin/admin.html:9
 msgid "Admin journal"
 msgstr "Diário do administrador"
 
 #: rhodecode/templates/admin/admin_log.html:6
+#: rhodecode/templates/admin/repos/repos.html:74
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
+#: rhodecode/templates/journal/journal.html:61
+#: rhodecode/templates/journal/journal.html:62
 msgid "Action"
 msgstr "Ação"
 
@@ -1127,6 +1643,11 @@ msgid "Repository"
 msgstr "Repositório"
 
 #: rhodecode/templates/admin/admin_log.html:8
+#: rhodecode/templates/bookmarks/bookmarks.html:37
+#: rhodecode/templates/bookmarks/bookmarks_data.html:7
+#: rhodecode/templates/branches/branches.html:52
+#: rhodecode/templates/tags/tags.html:37
+#: rhodecode/templates/tags/tags_data.html:7
 msgid "Date"
 msgstr "Data"
 
@@ -1134,7 +1655,7 @@ msgstr "Data"
 msgid "From IP"
 msgstr "A partir do IP"
 
-#: rhodecode/templates/admin/admin_log.html:52
+#: rhodecode/templates/admin/admin_log.html:53
 msgid "No actions yet"
 msgstr "Ainda não há ações"
 
@@ -1211,23 +1732,64 @@ msgid "E-mail Attribute"
 msgstr "Atributo de E-mail"
 
 #: rhodecode/templates/admin/ldap/ldap.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:141
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
 #: rhodecode/templates/admin/settings/hooks.html:73
-#: rhodecode/templates/admin/users/user_edit.html:117
-#: rhodecode/templates/admin/users/user_edit.html:142
-#: rhodecode/templates/admin/users/user_edit_my_account.html:89
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:263
+#: rhodecode/templates/admin/users/user_edit.html:129
+#: rhodecode/templates/admin/users/user_edit.html:174
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:135
+#: rhodecode/templates/settings/repo_settings.html:93
 msgid "Save"
 msgstr "Salvar"
 
+#: rhodecode/templates/admin/notifications/notifications.html:5
+#: rhodecode/templates/admin/notifications/notifications.html:9
+msgid "My Notifications"
+msgstr "Minhas Notificações"
+
+#: rhodecode/templates/admin/notifications/notifications.html:29
+msgid "All"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:30
+#, fuzzy
+msgid "Comments"
+msgstr "commits"
+
+#: rhodecode/templates/admin/notifications/notifications.html:31
+#: rhodecode/templates/base/base.html:254
+#: rhodecode/templates/base/base.html:256
+msgid "Pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:35
+msgid "Mark all read"
+msgstr "Marcar tudo como lido"
+
+#: rhodecode/templates/admin/notifications/notifications_data.html:39
+msgid "No notifications here yet"
+msgstr "Ainda não há notificações aqui"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:5
+#: rhodecode/templates/admin/notifications/show_notification.html:11
+msgid "Show notification"
+msgstr "Mostrar notificação"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:9
+msgid "Notifications"
+msgstr "Notificações"
+
 #: rhodecode/templates/admin/permissions/permissions.html:5
 msgid "Permissions administration"
 msgstr "Administração de permissões"
 
 #: rhodecode/templates/admin/permissions/permissions.html:11
-#: rhodecode/templates/admin/repos/repo_edit.html:109
-#: rhodecode/templates/admin/users/user_edit.html:127
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:248
-#: rhodecode/templates/settings/repo_settings.html:58
+#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
+#: rhodecode/templates/admin/users/user_edit.html:139
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:100
+#: rhodecode/templates/settings/repo_settings.html:86
 msgid "Permissions"
 msgstr "Permissões"
 
@@ -1244,8 +1806,14 @@ msgid "Repository permission"
 msgstr "Permissão de repositório"
 
 #: 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 "Todas as permissões padrão em cada repositório serão reinicializadas para as permissões escolhidas. Note que todas as permissões padrão customizadas nos repositórios serão perdidas"
+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 ""
+"Todas as permissões padrão em cada repositório serão reinicializadas para"
+" as permissões escolhidas. Note que todas as permissões padrão "
+"customizadas nos repositórios serão perdidas"
 
 #: rhodecode/templates/admin/permissions/permissions.html:50
 msgid "overwrite existing settings"
@@ -1260,6 +1828,12 @@ msgid "Repository creation"
 msgstr "Criação de repositório"
 
 #: rhodecode/templates/admin/permissions/permissions.html:71
+#, fuzzy
+msgid "Repository forking"
+msgstr "Criação de repositório"
+
+#: rhodecode/templates/admin/permissions/permissions.html:78
+#: rhodecode/templates/admin/repos/repo_edit.html:241
 msgid "set"
 msgstr "ajustar"
 
@@ -1270,7 +1844,6 @@ msgstr "Adicionar repositório"
 
 #: rhodecode/templates/admin/repos/repo_add.html:11
 #: rhodecode/templates/admin/repos/repo_edit.html:11
-#: rhodecode/templates/admin/repos/repos.html:10
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
 msgid "Repositories"
 msgstr "Repositórios"
@@ -1280,30 +1853,76 @@ msgid "add new"
 msgstr "adicionar novo"
 
 #: rhodecode/templates/admin/repos/repo_add_base.html:20
-#: rhodecode/templates/summary/summary.html:80
-#: rhodecode/templates/summary/summary.html:82
+#: rhodecode/templates/summary/summary.html:95
+#: rhodecode/templates/summary/summary.html:96
 msgid "Clone from"
 msgstr "Clonar de"
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:28
-#: rhodecode/templates/admin/repos/repo_edit.html:48
+#: rhodecode/templates/admin/repos/repo_add_base.html:24
+#: rhodecode/templates/admin/repos/repo_edit.html:44
+#: rhodecode/templates/settings/repo_settings.html:43
+msgid "Optional http[s] url from which repository should be cloned."
+msgstr "URL opcional http[s] da qual o repositório deve ser clonado."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:29
+#: rhodecode/templates/admin/repos/repo_edit.html:49
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:4
+#: rhodecode/templates/forks/fork.html:50
+#: rhodecode/templates/settings/repo_settings.html:48
 msgid "Repository group"
 msgstr "Grupo de repositórios"
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:36
-#: rhodecode/templates/admin/repos/repo_edit.html:56
+#: rhodecode/templates/admin/repos/repo_add_base.html:33
+#: rhodecode/templates/forks/fork.html:54
+#, fuzzy
+msgid "Optionaly select a group to put this repository into."
+msgstr "Opcionalmente selecione um grupo no qual colocar esse repositório."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:38
+#: rhodecode/templates/admin/repos/repo_edit.html:58
 msgid "Type"
 msgstr "Tipo"
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:52
-#: rhodecode/templates/admin/repos/repo_edit.html:73
-#: rhodecode/templates/settings/repo_fork.html:48
-#: rhodecode/templates/settings/repo_settings.html:49
-msgid "Private"
-msgstr "Privado"
-
-#: rhodecode/templates/admin/repos/repo_add_base.html:59
+#: rhodecode/templates/admin/repos/repo_add_base.html:42
+msgid "Type of repository to create."
+msgstr "Tipo de repositório a criar."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:47
+#: rhodecode/templates/admin/repos/repo_edit.html:66
+#: rhodecode/templates/forks/fork.html:41
+#: rhodecode/templates/settings/repo_settings.html:57
+#, fuzzy
+msgid "Landing revision"
+msgstr "próxima revisão"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:51
+#: rhodecode/templates/admin/repos/repo_edit.html:70
+#: rhodecode/templates/forks/fork.html:45
+#: rhodecode/templates/settings/repo_settings.html:61
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:60
+#: 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."
+msgstr ""
+"Seja sucinto e objetivo. Use um arquivo README para descrições mais "
+"longas."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:69
+#: 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 ""
+"Repositórios privados são visíveis somente por pessoas explicitamente "
+"adicionadas como colaboradores."
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:73
 msgid "add"
 msgstr "adicionar"
 
@@ -1317,183 +1936,283 @@ msgstr "Editar repositório"
 
 #: rhodecode/templates/admin/repos/repo_edit.html:13
 #: rhodecode/templates/admin/users/user_edit.html:13
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
+#: rhodecode/templates/admin/users/user_edit.html:224
+#: rhodecode/templates/admin/users/user_edit.html:226
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:13
-#: rhodecode/templates/files/files_annotate.html:49
-#: rhodecode/templates/files/files_source.html:20
+#: rhodecode/templates/files/files_source.html:44
+#: rhodecode/templates/journal/journal.html:81
 msgid "edit"
 msgstr "editar"
 
 #: rhodecode/templates/admin/repos/repo_edit.html:40
+#: rhodecode/templates/settings/repo_settings.html:39
 msgid "Clone uri"
 msgstr "URI de clonagem"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:81
+#: rhodecode/templates/admin/repos/repo_edit.html:53
+#: rhodecode/templates/settings/repo_settings.html:52
+msgid "Optional select a group to put this repository into."
+msgstr "Opcionalmente selecione um grupo no qual colocar esse repositório."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:94
 msgid "Enable statistics"
 msgstr "Habilitar estatísticas"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:98
+msgid "Enable statistics window on summary page."
+msgstr "Habilitar janela de estatísticas na página de sumário."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:103
 msgid "Enable downloads"
 msgstr "Habilitar downloads"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:127
+#: rhodecode/templates/admin/repos/repo_edit.html:107
+msgid "Enable download menu on summary page."
+msgstr "Habilitar menu de descarregar na página de sumário."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:112
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
+#, fuzzy
+msgid "Enable locking"
+msgstr "habilitar"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:116
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:126
+msgid "Change owner of this repository."
+msgstr "Mudar o dono desse repositório."
+
+#: rhodecode/templates/admin/repos/repo_edit.html:142
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
+#: rhodecode/templates/admin/settings/settings.html:113
+#: rhodecode/templates/admin/settings/settings.html:168
+#: rhodecode/templates/admin/settings/settings.html:258
+#: rhodecode/templates/admin/users/user_edit.html:130
+#: rhodecode/templates/admin/users/user_edit.html:175
+#: rhodecode/templates/admin/users/user_edit.html:278
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:136
+#: rhodecode/templates/files/files_add.html:82
+#: rhodecode/templates/files/files_edit.html:68
+#: rhodecode/templates/pullrequests/pullrequest.html:124
+#: rhodecode/templates/settings/repo_settings.html:94
+msgid "Reset"
+msgstr "Limpar"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:152
 msgid "Administration"
 msgstr "Administração"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:130
+#: rhodecode/templates/admin/repos/repo_edit.html:155
 msgid "Statistics"
 msgstr "Estatísticas"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Reset current statistics"
 msgstr "Reinicializar estatísticas atuais"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Confirm to remove current statistics"
 msgstr "Confirma remover atuais estatísticas"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:137
+#: rhodecode/templates/admin/repos/repo_edit.html:162
 msgid "Fetched to rev"
 msgstr "Trazida à rev"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:138
-msgid "Percentage of stats gathered"
-msgstr "Porcentagem das estatísticas totalizadas"
-
-#: rhodecode/templates/admin/repos/repo_edit.html:147
+#: rhodecode/templates/admin/repos/repo_edit.html:163
+msgid "Stats gathered"
+msgstr "Estatísticas coletadas"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:171
 msgid "Remote"
 msgstr "Remoto"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:151
+#: rhodecode/templates/admin/repos/repo_edit.html:175
 msgid "Pull changes from remote location"
 msgstr "Realizar pull de alterações a partir de localização remota"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:151
+#: rhodecode/templates/admin/repos/repo_edit.html:175
 msgid "Confirm to pull changes from remote side"
 msgstr "Confirma realizar pull de alterações a partir de lado remoto"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:162
+#: rhodecode/templates/admin/repos/repo_edit.html:186
 msgid "Cache"
 msgstr "Cache"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Invalidate repository cache"
 msgstr "Invalidar cache do repositório"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Confirm to invalidate repository cache"
 msgstr "Confirma invalidar cache do repositório"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:177
+#: rhodecode/templates/admin/repos/repo_edit.html:195
+#: rhodecode/templates/base/base.html:318
+#: rhodecode/templates/base/base.html:320
+#: rhodecode/templates/base/base.html:322
+msgid "Public journal"
+msgstr "Diário público"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:201
 msgid "Remove from public journal"
 msgstr "Remover do diário público"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:179
+#: rhodecode/templates/admin/repos/repo_edit.html:203
 msgid "Add to public journal"
 msgstr "Adicionar ao diário público"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:185
+#: rhodecode/templates/admin/repos/repo_edit.html:208
+msgid ""
+"All actions made on this repository will be accessible to everyone in "
+"public journal"
+msgstr ""
+"Todas as ações feitas nesse repositório serão acessíveis a todos no "
+"diário público"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:215
+#, fuzzy
+msgid "Locking"
+msgstr "destravar"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Unlock locked repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+#, fuzzy
+msgid "Confirm to unlock repository"
+msgstr "Confirma excluir este repositório"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "lock repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+#, fuzzy
+msgid "Confirm to lock repository"
+msgstr "Confirma excluir este repositório"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:224
+#, fuzzy
+msgid "Repository is not locked"
+msgstr "repositórios"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:229
+msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:236
+#, fuzzy
+msgid "Set as fork of"
+msgstr "Marcar como bifurcação"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:245
+#, fuzzy
+msgid "Manually set this repository as a fork of another from the list"
+msgstr "Marcar manualmente este repositório como sendo uma bifurcação de outro"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:251
+#: rhodecode/templates/changeset/changeset_file_comment.html:26
 msgid "Delete"
 msgstr "Excluir"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
+#: rhodecode/templates/admin/repos/repo_edit.html:255
 msgid "Remove this repository"
 msgstr "Remover deste repositório"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
-#: rhodecode/templates/admin/repos/repos.html:79
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+#: rhodecode/templates/journal/journal.html:84
 msgid "Confirm to delete this repository"
 msgstr "Confirma excluir este repositório"
 
+#: rhodecode/templates/admin/repos/repo_edit.html:259
+msgid ""
+"This repository will be renamed in a special way in order to be "
+"unaccesible for RhodeCode and VCS systems.\n"
+"                         If you need fully delete it from filesystem "
+"please do it manually"
+msgstr ""
+"Este repositório será renomeado de uma maneira especial, de forma a ser "
+"inacessível ao RhodeCode e sistemas de controle de versão.\n"
+"                         Se você precisa exclui-lo completamente do "
+"sistema de arquivos, por favor faça-o manualmente"
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:3
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
 msgid "none"
 msgstr "nenhum"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:4
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
 msgid "read"
 msgstr "ler"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:5
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
 msgid "write"
 msgstr "escrever"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:6
-#: rhodecode/templates/admin/users/users.html:38
-#: rhodecode/templates/base/base.html:296
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
+#: rhodecode/templates/admin/users/users.html:85
+#: rhodecode/templates/base/base.html:217
 msgid "admin"
 msgstr "administrador"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:7
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
 msgid "member"
 msgstr "membro"
 
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
+#: rhodecode/templates/data_table/_dt_elements.html:67
+#: rhodecode/templates/journal/journal.html:132
+#: rhodecode/templates/summary/summary.html:76
+msgid "private repository"
+msgstr "repositório privado"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:19
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:28
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
+#, fuzzy
+msgid "default"
+msgstr "excluir"
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:33
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:53
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:58
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
 msgid "revoke"
 msgstr "revogar"
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:75
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:83
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
 msgid "Add another member"
 msgstr "Adicionar outro membro"
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:89
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:97
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
 msgid "Failed to remove user"
 msgstr "Falha ao reomver usuário"
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:104
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:112
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
 msgid "Failed to remove users group"
 msgstr "Falha ao remover grupo de usuários"
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:205
-msgid "Group"
-msgstr "Grupo"
-
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:206
-#: rhodecode/templates/admin/users_groups/users_groups.html:33
-msgid "members"
-msgstr "membros"
-
 #: rhodecode/templates/admin/repos/repos.html:5
 msgid "Repositories administration"
 msgstr "Administração de repositórios"
 
-#: rhodecode/templates/admin/repos/repos.html:34
-#: rhodecode/templates/summary/summary.html:100
-msgid "Contact"
-msgstr "Contato"
-
-#: rhodecode/templates/admin/repos/repos.html:35
-#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
-#: rhodecode/templates/admin/users/user_edit_my_account.html:119
-#: rhodecode/templates/admin/users/users.html:40
-#: rhodecode/templates/admin/users_groups/users_groups.html:35
-msgid "action"
-msgstr "ação"
-
-#: rhodecode/templates/admin/repos/repos.html:51
-#: rhodecode/templates/admin/users/user_edit_my_account.html:134
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
-msgid "private"
-msgstr "privado"
-
-#: rhodecode/templates/admin/repos/repos.html:53
-#: rhodecode/templates/admin/repos/repos.html:59
-#: rhodecode/templates/admin/users/user_edit_my_account.html:136
-#: rhodecode/templates/admin/users/user_edit_my_account.html:142
-#: rhodecode/templates/summary/summary.html:68
-msgid "public"
-msgstr "público"
-
-#: rhodecode/templates/admin/repos/repos.html:79
-#: rhodecode/templates/admin/users/users.html:55
-msgid "delete"
-msgstr "excluir"
-
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:8
 msgid "Groups"
 msgstr "Grupos"
 
-#: rhodecode/templates/admin/repos_groups/repos_groups.html:13
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:12
 msgid "with"
 msgstr "com"
 
@@ -1516,10 +2235,10 @@ msgid "Group parent"
 msgstr "Progenitor do grupo"
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
-#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
-#: rhodecode/templates/admin/users/user_add.html:85
+#: rhodecode/templates/admin/users/user_add.html:94
 #: rhodecode/templates/admin/users_groups/users_group_add.html:49
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:90
+#: rhodecode/templates/pullrequests/pullrequest_show.html:113
 msgid "save"
 msgstr "salvar"
 
@@ -1531,6 +2250,12 @@ msgstr "Editar grupo de repositórios"
 msgid "edit repos group"
 msgstr "editar grupo de repositórios"
 
+#: 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 ""
+
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
 msgid "Repositories groups administration"
 msgstr "Administração de grupos de repositórios"
@@ -1540,12 +2265,27 @@ msgid "ADD NEW GROUP"
 msgstr "ADICIONAR NOVO GRUPO"
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
-msgid "Number of repositories"
-msgstr "Número de repositórios"
+msgid "Number of toplevel repositories"
+msgstr "Número de repositórios de nível superior"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
+#: rhodecode/templates/admin/users/users.html:87
+#: rhodecode/templates/admin/users_groups/users_groups.html:35
+msgid "action"
+msgstr "ação"
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
-msgid "Confirm to delete this group"
-msgstr "Confirme para excluir este grupo"
+#: rhodecode/templates/admin/users/user_edit.html:255
+#: rhodecode/templates/admin/users_groups/users_groups.html:44
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#: rhodecode/templates/data_table/_dt_elements.html:103
+msgid "delete"
+msgstr "excluir"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#, python-format
+msgid "Confirm to delete this group: %s"
+msgstr "Confirme para excluir esse grupo: %s"
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
 msgid "There are no repositories groups yet"
@@ -1558,7 +2298,6 @@ msgstr "Administração de configurações"
 
 #: rhodecode/templates/admin/settings/hooks.html:9
 #: rhodecode/templates/admin/settings/settings.html:9
-#: rhodecode/templates/settings/repo_settings.html:5
 #: rhodecode/templates/settings/repo_settings.html:13
 msgid "Settings"
 msgstr "Configurações"
@@ -1588,119 +2327,208 @@ msgid "rescan option"
 msgstr "opção de varredura"
 
 #: 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 "Caso um repositório tenha sido excluído do sistema de arquivos e haja restos no banco de dados, marque esta opção para varrer dados obsoletos no banco e removê-los."
+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 ""
+"Caso um repositório tenha sido excluído do sistema de arquivos e haja "
+"restos no banco de dados, marque esta opção para varrer dados obsoletos "
+"no banco e removê-los."
 
 #: rhodecode/templates/admin/settings/settings.html:39
 msgid "destroy old data"
 msgstr "destruir dados antigos"
 
-#: rhodecode/templates/admin/settings/settings.html:45
+#: rhodecode/templates/admin/settings/settings.html:41
+msgid ""
+"Rescan repositories location for new repositories. Also deletes obsolete "
+"if `destroy` flag is checked "
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:46
 msgid "Rescan repositories"
 msgstr "Varrer repositórios"
 
-#: rhodecode/templates/admin/settings/settings.html:51
+#: rhodecode/templates/admin/settings/settings.html:52
 msgid "Whoosh indexing"
 msgstr "Indexação do Whoosh"
 
-#: rhodecode/templates/admin/settings/settings.html:59
+#: rhodecode/templates/admin/settings/settings.html:60
 msgid "index build option"
 msgstr "opção de construção de índice"
 
-#: rhodecode/templates/admin/settings/settings.html:64
+#: rhodecode/templates/admin/settings/settings.html:65
 msgid "build from scratch"
 msgstr "construir do início"
 
-#: rhodecode/templates/admin/settings/settings.html:70
+#: rhodecode/templates/admin/settings/settings.html:71
 msgid "Reindex"
 msgstr "Reindexar"
 
-#: rhodecode/templates/admin/settings/settings.html:76
+#: rhodecode/templates/admin/settings/settings.html:77
 msgid "Global application settings"
 msgstr "Configurações globais da aplicação"
 
-#: rhodecode/templates/admin/settings/settings.html:85
+#: rhodecode/templates/admin/settings/settings.html:86
 msgid "Application name"
 msgstr "Nome da aplicação"
 
-#: rhodecode/templates/admin/settings/settings.html:94
+#: rhodecode/templates/admin/settings/settings.html:95
 msgid "Realm text"
 msgstr "Texto de esfera"
 
-#: rhodecode/templates/admin/settings/settings.html:103
+#: rhodecode/templates/admin/settings/settings.html:104
 msgid "GA code"
 msgstr "Código GA"
 
-#: rhodecode/templates/admin/settings/settings.html:111
-#: rhodecode/templates/admin/settings/settings.html:177
-msgid "Save settings"
-msgstr "Salvar configurações"
-
 #: rhodecode/templates/admin/settings/settings.html:112
-#: rhodecode/templates/admin/settings/settings.html:178
-#: rhodecode/templates/admin/users/user_edit.html:118
-#: rhodecode/templates/admin/users/user_edit.html:143
-#: rhodecode/templates/admin/users/user_edit_my_account.html:90
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:264
-#: rhodecode/templates/files/files_edit.html:50
-msgid "Reset"
-msgstr "Limpar"
-
-#: rhodecode/templates/admin/settings/settings.html:118
-msgid "Mercurial settings"
-msgstr "Configurações do Mercurial"
-
-#: rhodecode/templates/admin/settings/settings.html:127
+#: rhodecode/templates/admin/settings/settings.html:167
+#: rhodecode/templates/admin/settings/settings.html:257
+msgid "Save settings"
+msgstr "Salvar configurações"
+
+#: rhodecode/templates/admin/settings/settings.html:119
+#, fuzzy
+msgid "Visualisation settings"
+msgstr "Configurações globais da aplicação"
+
+#: rhodecode/templates/admin/settings/settings.html:128
+#, fuzzy
+msgid "Icons"
+msgstr "Opções"
+
+#: rhodecode/templates/admin/settings/settings.html:133
+msgid "Show public repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:137
+#, fuzzy
+msgid "Show private repo icon on repositories"
+msgstr "repositório privado"
+
+#: rhodecode/templates/admin/settings/settings.html:144
+#, fuzzy
+msgid "Meta-Tagging"
+msgstr "configurações"
+
+#: rhodecode/templates/admin/settings/settings.html:149
+msgid "Stylify recognised metatags:"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:176
+#, fuzzy
+msgid "VCS settings"
+msgstr "configurações"
+
+#: rhodecode/templates/admin/settings/settings.html:185
 msgid "Web"
 msgstr "Web"
 
-#: rhodecode/templates/admin/settings/settings.html:132
-msgid "require ssl for pushing"
+#: rhodecode/templates/admin/settings/settings.html:190
+#, fuzzy
+msgid "require ssl for vcs operations"
 msgstr "exigir ssl para realizar push"
 
-#: rhodecode/templates/admin/settings/settings.html:139
+#: rhodecode/templates/admin/settings/settings.html:192
+msgid ""
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
+"will return HTTP Error 406: Not Acceptable"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:198
 msgid "Hooks"
 msgstr "Ganchos"
 
-#: rhodecode/templates/admin/settings/settings.html:142
+#: rhodecode/templates/admin/settings/settings.html:203
+msgid "Update repository after push (hg update)"
+msgstr "Atualizar repositório após realizar push (hg update)"
+
+#: rhodecode/templates/admin/settings/settings.html:207
+msgid "Show repository size after push"
+msgstr "Mostrar tamanho do repositório após o push"
+
+#: rhodecode/templates/admin/settings/settings.html:211
+msgid "Log user push commands"
+msgstr "Armazenar registro de comandos de push dos usuários"
+
+#: rhodecode/templates/admin/settings/settings.html:215
+msgid "Log user pull commands"
+msgstr "Armazenar registro de comandos de pull dos usuários"
+
+#: rhodecode/templates/admin/settings/settings.html:219
 msgid "advanced setup"
 msgstr "confirguações avançadas"
 
-#: rhodecode/templates/admin/settings/settings.html:147
-msgid "Update repository after push (hg update)"
-msgstr "Atualizar repositório após realizar push (hg update)"
-
-#: rhodecode/templates/admin/settings/settings.html:151
-msgid "Show repository size after push"
-msgstr "Mostrar tamanho do repositório após o push"
-
-#: rhodecode/templates/admin/settings/settings.html:155
-msgid "Log user push commands"
-msgstr "Armazenar registro de comandos de push dos usuários"
-
-#: rhodecode/templates/admin/settings/settings.html:159
-msgid "Log user pull commands"
-msgstr "Armazenar registro de comandos de pull dos usuários"
-
-#: rhodecode/templates/admin/settings/settings.html:166
+#: rhodecode/templates/admin/settings/settings.html:224
+#, fuzzy
+msgid "Mercurial Extensions"
+msgstr "Repositório Mercurial"
+
+#: rhodecode/templates/admin/settings/settings.html:229
+msgid "largefiles extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:233
+msgid "hgsubversion extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:235
+msgid ""
+"Requires hgsubversion library installed. Allows clonning from svn remote "
+"locations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:245
 msgid "Repositories location"
 msgstr "Localização dos repositórios"
 
-#: rhodecode/templates/admin/settings/settings.html:171
-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 "Essa é uma configuração crucial da aplicação. Se você realmente tem certeza de que quer mudar isto, você precisa reiniciar a aplicação para que essa configuração tenha efeito. Clique este rótulo para destravar."
-
-#: rhodecode/templates/admin/settings/settings.html:172
+#: rhodecode/templates/admin/settings/settings.html:250
+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 ""
+"Essa é uma configuração crucial da aplicação. Se você realmente tem "
+"certeza de que quer mudar isto, você precisa reiniciar a aplicação para "
+"que essa configuração tenha efeito. Clique este rótulo para destravar."
+
+#: rhodecode/templates/admin/settings/settings.html:251
 msgid "unlock"
 msgstr "destravar"
 
+#: rhodecode/templates/admin/settings/settings.html:252
+msgid ""
+"Location where repositories are stored. After changing this value a "
+"restart, and rescan is required"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:272
+msgid "Test Email"
+msgstr "Testar E-mail"
+
+#: rhodecode/templates/admin/settings/settings.html:280
+msgid "Email to"
+msgstr "E-mail para"
+
+#: rhodecode/templates/admin/settings/settings.html:288
+msgid "Send"
+msgstr "Enviar"
+
+#: rhodecode/templates/admin/settings/settings.html:294
+msgid "System Info and Packages"
+msgstr "Informações de Sistema e Pacotes"
+
+#: rhodecode/templates/admin/settings/settings.html:297
+msgid "show"
+msgstr "mostrar"
+
 #: rhodecode/templates/admin/users/user_add.html:5
 msgid "Add user"
 msgstr "Adicionar usuário"
 
 #: rhodecode/templates/admin/users/user_add.html:10
 #: rhodecode/templates/admin/users/user_edit.html:11
-#: rhodecode/templates/admin/users/users.html:9
 msgid "Users"
 msgstr "Usuários"
 
@@ -1708,8 +2536,12 @@ msgstr "Usuários"
 msgid "add new user"
 msgstr "adicionar novo usuário"
 
-#: rhodecode/templates/admin/users/user_add.html:77
-#: rhodecode/templates/admin/users/user_edit.html:101
+#: rhodecode/templates/admin/users/user_add.html:50
+msgid "Password confirmation"
+msgstr "Confirmação de senha"
+
+#: rhodecode/templates/admin/users/user_add.html:86
+#: rhodecode/templates/admin/users/user_edit.html:113
 #: rhodecode/templates/admin/users_groups/users_group_add.html:41
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:42
 msgid "Active"
@@ -1719,36 +2551,100 @@ msgstr "Ativo"
 msgid "Edit user"
 msgstr "Editar usuário"
 
-#: rhodecode/templates/admin/users/user_edit.html:33
-#: rhodecode/templates/admin/users/user_edit_my_account.html:32
+#: rhodecode/templates/admin/users/user_edit.html:34
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
 msgid "Change your avatar at"
 msgstr "Altere o seu avatar em"
 
-#: rhodecode/templates/admin/users/user_edit.html:34
-#: rhodecode/templates/admin/users/user_edit_my_account.html:33
+#: rhodecode/templates/admin/users/user_edit.html:35
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
 msgid "Using"
 msgstr "Usando"
 
-#: rhodecode/templates/admin/users/user_edit.html:40
-#: rhodecode/templates/admin/users/user_edit_my_account.html:39
+#: rhodecode/templates/admin/users/user_edit.html:43
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
 msgid "API key"
 msgstr "Chave de API"
 
-#: rhodecode/templates/admin/users/user_edit.html:56
+#: rhodecode/templates/admin/users/user_edit.html:59
 msgid "LDAP DN"
 msgstr "DN LDAP"
 
-#: rhodecode/templates/admin/users/user_edit.html:65
-#: rhodecode/templates/admin/users/user_edit_my_account.html:54
+#: rhodecode/templates/admin/users/user_edit.html:68
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
 msgid "New password"
 msgstr "Nova senha"
 
-#: rhodecode/templates/admin/users/user_edit.html:135
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:256
+#: rhodecode/templates/admin/users/user_edit.html:77
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
+msgid "New password confirmation"
+msgstr "Confirmação de nova senha"
+
+#: rhodecode/templates/admin/users/user_edit.html:147
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:108
+#, fuzzy
+msgid "Inherit default permissions"
+msgstr "Permissões padrão"
+
+#: rhodecode/templates/admin/users/user_edit.html:152
+#: 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 ""
+
+#: rhodecode/templates/admin/users/user_edit.html:158
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
 msgid "Create repositories"
 msgstr "Criar repositórios"
 
+#: rhodecode/templates/admin/users/user_edit.html:166
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:127
+#, fuzzy
+msgid "Fork repositories"
+msgstr "repositórios"
+
+#: rhodecode/templates/admin/users/user_edit.html:186
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
+#, fuzzy
+msgid "Nothing here yet"
+msgstr "Ainda não há notificações aqui"
+
+#: rhodecode/templates/admin/users/user_edit.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account.html:60
+#: rhodecode/templates/admin/users/user_edit_my_account.html:194
+msgid "Permission"
+msgstr "Permissão"
+
+#: rhodecode/templates/admin/users/user_edit.html:194
+#, fuzzy
+msgid "Edit Permission"
+msgstr "Permissão de repositório"
+
+#: rhodecode/templates/admin/users/user_edit.html:243
+#, fuzzy
+msgid "Email addresses"
+msgstr "Endereço de e-mail"
+
+#: rhodecode/templates/admin/users/user_edit.html:256
+#, fuzzy, python-format
+msgid "Confirm to delete this email: %s"
+msgstr "Confirma excluir este usuário: %s"
+
+#: rhodecode/templates/admin/users/user_edit.html:270
+#, fuzzy
+msgid "New email address"
+msgstr "Endereço de e-mail"
+
+#: rhodecode/templates/admin/users/user_edit.html:277
+#, fuzzy
+msgid "Add"
+msgstr "adicionar"
+
 #: rhodecode/templates/admin/users/user_edit_my_account.html:5
+#: rhodecode/templates/base/base.html:124
 msgid "My account"
 msgstr "Minha conta"
 
@@ -1756,26 +2652,77 @@ msgstr "Minha conta"
 msgid "My Account"
 msgstr "Minha Conta"
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:101
-msgid "My repositories"
+#: rhodecode/templates/admin/users/user_edit_my_account.html:35
+msgid "My permissions"
+msgstr "Minhas permissões"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:38
+#: rhodecode/templates/journal/journal.html:41
+msgid "My repos"
 msgstr "Meus repositórios"
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:107
-msgid "ADD REPOSITORY"
-msgstr "ADICIONAR REPOSITÓRIO"
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:118
-#: rhodecode/templates/branches/branches_data.html:7
-#: rhodecode/templates/shortlog/shortlog_data.html:8
-#: rhodecode/templates/tags/tags_data.html:7
-msgid "revision"
-msgstr "revisão"
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:41
+#, fuzzy
+msgid "My pull requests"
+msgstr "comentado no commit"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:45
+#, fuzzy
+msgid "Add repo"
+msgstr "adicionar novo"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
+msgid "Opened by me"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
+#, python-format
+msgid "Pull request #%s opened on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
+#, fuzzy
+msgid "Confirm to delete this pull request"
+msgstr "Confirma excluir este repositório"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
+msgid "I participate in"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
+#, python-format
+msgid "Pull request #%s opened by %s on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
+#: rhodecode/templates/bookmarks/bookmarks.html:40
+#: rhodecode/templates/bookmarks/bookmarks_data.html:9
+#: rhodecode/templates/branches/branches.html:55
+#: rhodecode/templates/journal/journal.html:60
+#: rhodecode/templates/tags/tags.html:40
+#: rhodecode/templates/tags/tags_data.html:9
+msgid "Revision"
+msgstr "Revisão"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/journal/journal.html:81
+msgid "private"
+msgstr "privado"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr "Confirma excluir esse repositório: %s"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
+#: rhodecode/templates/journal/journal.html:94
 msgid "No repositories yet"
 msgstr "Ainda não há repositórios"
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
+#: rhodecode/templates/journal/journal.html:96
 msgid "create one now"
 msgstr "criar um agora"
 
@@ -1783,42 +2730,42 @@ msgstr "criar um agora"
 msgid "Users administration"
 msgstr "Administração de usuários"
 
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/base/base.html:223
+msgid "users"
+msgstr "usuários"
+
 #: rhodecode/templates/admin/users/users.html:23
 msgid "ADD NEW USER"
 msgstr "ADICIONAR NOVO USUÁRIO"
 
-#: rhodecode/templates/admin/users/users.html:33
+#: rhodecode/templates/admin/users/users.html:77
 msgid "username"
 msgstr "nome de usuário"
 
-#: rhodecode/templates/admin/users/users.html:34
-#: rhodecode/templates/branches/branches_data.html:5
-#: rhodecode/templates/tags/tags_data.html:5
-msgid "name"
-msgstr "nome"
-
-#: rhodecode/templates/admin/users/users.html:35
+#: rhodecode/templates/admin/users/users.html:80
+#, fuzzy
+msgid "firstname"
+msgstr "Primeiro Nome"
+
+#: rhodecode/templates/admin/users/users.html:81
 msgid "lastname"
 msgstr "sobrenome"
 
-#: rhodecode/templates/admin/users/users.html:36
+#: rhodecode/templates/admin/users/users.html:82
 msgid "last login"
 msgstr "último login"
 
-#: rhodecode/templates/admin/users/users.html:37
+#: rhodecode/templates/admin/users/users.html:84
 #: rhodecode/templates/admin/users_groups/users_groups.html:34
 msgid "active"
 msgstr "ativo"
 
-#: rhodecode/templates/admin/users/users.html:39
-#: rhodecode/templates/base/base.html:305
+#: rhodecode/templates/admin/users/users.html:86
+#: rhodecode/templates/base/base.html:226
 msgid "ldap"
 msgstr "ldap"
 
-#: rhodecode/templates/admin/users/users.html:56
-msgid "Confirm to delete this user"
-msgstr "Conforma excluir este usuário"
-
 #: rhodecode/templates/admin/users_groups/users_group_add.html:5
 msgid "Add users group"
 msgstr "Adicionar grupo de usuários"
@@ -1860,6 +2807,10 @@ msgstr "Membros disponíveis"
 msgid "Add all elements"
 msgstr "Adicionar todos os elementos"
 
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:146
+msgid "Group members"
+msgstr "Membros do grupo"
+
 #: rhodecode/templates/admin/users_groups/users_groups.html:5
 msgid "Users groups administration"
 msgstr "Administração de grupos de usuários"
@@ -1872,399 +2823,627 @@ msgstr "ADICIONAR NOVO GRUPO DE USUÁRIOS"
 msgid "group name"
 msgstr "nome do grupo"
 
-#: rhodecode/templates/base/base.html:32
+#: rhodecode/templates/admin/users_groups/users_groups.html:33
+#: rhodecode/templates/base/root.html:46
+msgid "members"
+msgstr "membros"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:45
+#, python-format
+msgid "Confirm to delete this users group: %s"
+msgstr "Confirme para excluir este grupo de usuários: %s"
+
+#: rhodecode/templates/base/base.html:41
+msgid "Submit a bug"
+msgstr "Encaminhe um bug"
+
+#: rhodecode/templates/base/base.html:77
+msgid "Login to your account"
+msgstr "Entrar com sua conta"
+
+#: rhodecode/templates/base/base.html:100
 msgid "Forgot password ?"
 msgstr "Esqueceu a senha ?"
 
-#: rhodecode/templates/base/base.html:57
-#: rhodecode/templates/base/base.html:338
-#: rhodecode/templates/base/base.html:340
-#: rhodecode/templates/base/base.html:342
+#: rhodecode/templates/base/base.html:107
+msgid "Log In"
+msgstr "Entrar"
+
+#: rhodecode/templates/base/base.html:118
+msgid "Inbox"
+msgstr "Caixa de Entrada"
+
+#: rhodecode/templates/base/base.html:122
+#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/base/base.html:302
+#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/bookmarks/bookmarks.html:11
+#: rhodecode/templates/branches/branches.html:10
+#: rhodecode/templates/changelog/changelog.html:10
+#: rhodecode/templates/changeset/changeset.html:10
+#: rhodecode/templates/changeset/changeset_range.html:9
+#: rhodecode/templates/compare/compare_diff.html:9
+#: rhodecode/templates/files/file_diff.html:8
+#: rhodecode/templates/files/files.html:8
+#: 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/pullrequests/pullrequest.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
+#: rhodecode/templates/settings/repo_settings.html:9
+#: rhodecode/templates/shortlog/shortlog.html:10
+#: rhodecode/templates/summary/summary.html:8
+#: rhodecode/templates/tags/tags.html:11
 msgid "Home"
 msgstr "Início"
 
-#: rhodecode/templates/base/base.html:61
-#: rhodecode/templates/base/base.html:347
-#: rhodecode/templates/base/base.html:349
-#: rhodecode/templates/base/base.html:351
+#: rhodecode/templates/base/base.html:123
+#: rhodecode/templates/base/base.html:309
+#: rhodecode/templates/base/base.html:311
+#: rhodecode/templates/base/base.html:313
 #: rhodecode/templates/journal/journal.html:4
-#: rhodecode/templates/journal/journal.html:17
+#: rhodecode/templates/journal/journal.html:21
 #: rhodecode/templates/journal/public_journal.html:4
 msgid "Journal"
 msgstr "Diário"
 
-#: rhodecode/templates/base/base.html:66
-msgid "Login"
-msgstr "Entrar"
-
-#: rhodecode/templates/base/base.html:68
+#: rhodecode/templates/base/base.html:125
 msgid "Log Out"
 msgstr "Sair"
 
-#: rhodecode/templates/base/base.html:107
-msgid "Submit a bug"
-msgstr "Encaminhe um bug"
-
-#: rhodecode/templates/base/base.html:141
+#: rhodecode/templates/base/base.html:144
 msgid "Switch repository"
 msgstr "Trocar repositório"
 
-#: rhodecode/templates/base/base.html:143
+#: rhodecode/templates/base/base.html:146
 msgid "Products"
 msgstr "Produtos"
 
-#: rhodecode/templates/base/base.html:149
+#: rhodecode/templates/base/base.html:152
+#: rhodecode/templates/base/base.html:182
 msgid "loading..."
 msgstr "carregando..."
 
-#: rhodecode/templates/base/base.html:234
-#: rhodecode/templates/base/base.html:236
-#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:158
+#: rhodecode/templates/base/base.html:160
+#: rhodecode/templates/base/base.html:162
+#: rhodecode/templates/data_table/_dt_elements.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:17
+#: rhodecode/templates/data_table/_dt_elements.html:19
+msgid "Summary"
+msgstr "Sumário"
+
+#: rhodecode/templates/base/base.html:166
+#: rhodecode/templates/base/base.html:168
+#: rhodecode/templates/base/base.html:170
+#: rhodecode/templates/changelog/changelog.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:23
+#: rhodecode/templates/data_table/_dt_elements.html:25
+#: rhodecode/templates/data_table/_dt_elements.html:27
+msgid "Changelog"
+msgstr "Registro de alterações"
+
+#: rhodecode/templates/base/base.html:175
+#: rhodecode/templates/base/base.html:177
+#: rhodecode/templates/base/base.html:179
 msgid "Switch to"
 msgstr "Trocar para"
 
-#: rhodecode/templates/base/base.html:242
-#: rhodecode/templates/branches/branches.html:13
-msgid "branches"
-msgstr "ramos"
-
-#: rhodecode/templates/base/base.html:249
-#: rhodecode/templates/branches/branches_data.html:52
-msgid "There are no branches yet"
-msgstr "Ainda não há ramos"
-
-#: rhodecode/templates/base/base.html:254
-#: rhodecode/templates/shortlog/shortlog_data.html:10
-#: rhodecode/templates/tags/tags.html:14
-msgid "tags"
-msgstr "etiquetas"
-
-#: rhodecode/templates/base/base.html:261
-#: rhodecode/templates/tags/tags_data.html:32
-msgid "There are no tags yet"
-msgstr "Ainda não há etiquetas"
-
-#: rhodecode/templates/base/base.html:277
-#: rhodecode/templates/base/base.html:281
-#: rhodecode/templates/files/files_annotate.html:40
-#: rhodecode/templates/files/files_source.html:11
+#: rhodecode/templates/base/base.html:186
+#: rhodecode/templates/base/base.html:188
+#: rhodecode/templates/base/base.html:190
+#: rhodecode/templates/data_table/_dt_elements.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:33
+#: rhodecode/templates/data_table/_dt_elements.html:35
+msgid "Files"
+msgstr "Arquivos"
+
+#: rhodecode/templates/base/base.html:195
+#: rhodecode/templates/base/base.html:199
 msgid "Options"
 msgstr "Opções"
 
-#: rhodecode/templates/base/base.html:286
-#: rhodecode/templates/base/base.html:288
-#: rhodecode/templates/base/base.html:306
+#: rhodecode/templates/base/base.html:204
+#: rhodecode/templates/base/base.html:206
+#: rhodecode/templates/base/base.html:227
 msgid "settings"
 msgstr "configurações"
 
-#: rhodecode/templates/base/base.html:292
+#: rhodecode/templates/base/base.html:209
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/forks/fork.html:13
+msgid "fork"
+msgstr "bifurcação"
+
+#: rhodecode/templates/base/base.html:211
+#: rhodecode/templates/changelog/changelog.html:40
+msgid "Open new pull request"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:213
 msgid "search"
 msgstr "pesquisar"
 
-#: rhodecode/templates/base/base.html:299
-msgid "journal"
-msgstr "diário"
-
-#: rhodecode/templates/base/base.html:301
+#: rhodecode/templates/base/base.html:222
 msgid "repositories groups"
 msgstr "grupos de repositórios"
 
-#: rhodecode/templates/base/base.html:302
-msgid "users"
-msgstr "usuários"
-
-#: rhodecode/templates/base/base.html:303
+#: rhodecode/templates/base/base.html:224
 msgid "users groups"
 msgstr "grupos de usuários"
 
-#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/base/base.html:225
 msgid "permissions"
 msgstr "permissões"
 
-#: rhodecode/templates/base/base.html:317
-#: rhodecode/templates/base/base.html:319
-#: rhodecode/templates/followers/followers.html:5
+#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:240
 msgid "Followers"
 msgstr "Seguidores"
 
-#: rhodecode/templates/base/base.html:325
-#: rhodecode/templates/base/base.html:327
-#: rhodecode/templates/forks/forks.html:5
+#: rhodecode/templates/base/base.html:246
+#: rhodecode/templates/base/base.html:248
 msgid "Forks"
 msgstr "Bifurcações"
 
-#: rhodecode/templates/base/base.html:356
-#: rhodecode/templates/base/base.html:358
-#: rhodecode/templates/base/base.html:360
-#: rhodecode/templates/search/search.html:4
-#: rhodecode/templates/search/search.html:24
-#: rhodecode/templates/search/search.html:46
+#: rhodecode/templates/base/base.html:327
+#: rhodecode/templates/base/base.html:329
+#: rhodecode/templates/base/base.html:331
+#: rhodecode/templates/search/search.html:52
 msgid "Search"
 msgstr "Pesquisar"
 
-#: rhodecode/templates/base/root.html:57
-#: rhodecode/templates/journal/journal.html:48
-#: rhodecode/templates/summary/summary.html:36
+#: rhodecode/templates/base/root.html:42
+msgid "add another comment"
+msgstr "adicionar outro comentário"
+
+#: rhodecode/templates/base/root.html:43
+#: rhodecode/templates/journal/journal.html:120
+#: rhodecode/templates/summary/summary.html:57
 msgid "Stop following this repository"
 msgstr "Parar de seguir este repositório"
 
-#: rhodecode/templates/base/root.html:66
-#: rhodecode/templates/summary/summary.html:40
+#: rhodecode/templates/base/root.html:44
+#: rhodecode/templates/summary/summary.html:61
 msgid "Start following this repository"
 msgstr "Passar a seguir este repositório"
 
-#: rhodecode/templates/branches/branches_data.html:4
-#: rhodecode/templates/tags/tags_data.html:4
+#: rhodecode/templates/base/root.html:45
+msgid "Group"
+msgstr "Grupo"
+
+#: rhodecode/templates/base/root.html:47
+msgid "search truncated"
+msgstr "pesquisa truncada"
+
+#: rhodecode/templates/base/root.html:48
+msgid "no matching files"
+msgstr "nenhum arquivo corresponde"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:5
+#, fuzzy, python-format
+msgid "%s Bookmarks"
+msgstr "marcadores"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:39
+#: rhodecode/templates/bookmarks/bookmarks_data.html:8
+#: rhodecode/templates/branches/branches.html:54
+#: rhodecode/templates/tags/tags.html:39
+#: rhodecode/templates/tags/tags_data.html:8
+msgid "Author"
+msgstr "Autor"
+
+#: rhodecode/templates/branches/branches.html:5
+#, fuzzy, python-format
+msgid "%s Branches"
+msgstr "ramos"
+
+#: rhodecode/templates/branches/branches.html:29
+#, fuzzy
+msgid "Compare branches"
+msgstr "ramos"
+
+#: rhodecode/templates/branches/branches.html:57
+#: rhodecode/templates/compare/compare_diff.html:5
+#: rhodecode/templates/compare/compare_diff.html:13
+#, fuzzy
+msgid "Compare"
+msgstr "comparar exibir"
+
+#: rhodecode/templates/branches/branches_data.html:6
+msgid "name"
+msgstr "nome"
+
+#: rhodecode/templates/branches/branches_data.html:7
 msgid "date"
 msgstr "data"
 
-#: rhodecode/templates/branches/branches_data.html:6
-#: rhodecode/templates/shortlog/shortlog_data.html:7
-#: rhodecode/templates/tags/tags_data.html:6
-msgid "author"
-msgstr "autor"
-
 #: rhodecode/templates/branches/branches_data.html:8
-#: rhodecode/templates/shortlog/shortlog_data.html:11
-#: rhodecode/templates/tags/tags_data.html:8
-msgid "links"
-msgstr "inks"
-
-#: rhodecode/templates/branches/branches_data.html:23
-#: rhodecode/templates/branches/branches_data.html:43
-#: rhodecode/templates/shortlog/shortlog_data.html:39
-#: rhodecode/templates/tags/tags_data.html:24
-msgid "changeset"
-msgstr "conjunto de mudanças"
-
-#: rhodecode/templates/branches/branches_data.html:25
-#: rhodecode/templates/branches/branches_data.html:45
-#: rhodecode/templates/files/files.html:12
-#: rhodecode/templates/shortlog/shortlog_data.html:41
-#: rhodecode/templates/summary/summary.html:233
-#: rhodecode/templates/tags/tags_data.html:26
-msgid "files"
-msgstr "arquivos"
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "showing "
-msgstr "mostrando "
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "out of"
-msgstr "de"
+#: rhodecode/templates/shortlog/shortlog_data.html:8
+msgid "author"
+msgstr "autor"
+
+#: rhodecode/templates/branches/branches_data.html:9
+#: rhodecode/templates/shortlog/shortlog_data.html:5
+msgid "revision"
+msgstr "revisão"
+
+#: rhodecode/templates/branches/branches_data.html:10
+#, fuzzy
+msgid "compare"
+msgstr "comparar exibir"
+
+#: rhodecode/templates/changelog/changelog.html:6
+#, fuzzy, python-format
+msgid "%s Changelog"
+msgstr "Registro de alterações"
+
+#: rhodecode/templates/changelog/changelog.html:15
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] "mostrando %d de %d revisão"
+msgstr[1] "mostrando %d de %d revisões"
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:19
+#, python-format
+msgid "compare fork with %s"
+msgstr ""
 
 #: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:21
+#, fuzzy
+msgid "Compare fork"
+msgstr "comparar exibir"
+
+#: rhodecode/templates/changelog/changelog.html:46
 msgid "Show"
 msgstr "Mostrar"
 
-#: rhodecode/templates/changelog/changelog.html:50
-#: rhodecode/templates/changeset/changeset.html:42
-#: rhodecode/templates/summary/summary.html:609
-msgid "commit"
-msgstr "commit"
-
-#: rhodecode/templates/changelog/changelog.html:63
+#: rhodecode/templates/changelog/changelog.html:72
+#: rhodecode/templates/summary/summary.html:364
+msgid "show more"
+msgstr "mostrar mais"
+
+#: rhodecode/templates/changelog/changelog.html:76
 msgid "Affected number of files, click to show more details"
 msgstr "Número de arquivos afetados, clique para mostrar mais detalhes"
 
-#: rhodecode/templates/changelog/changelog.html:67
-#: rhodecode/templates/changeset/changeset.html:66
-msgid "merge"
-msgstr "mesclar"
-
-#: rhodecode/templates/changelog/changelog.html:72
-#: rhodecode/templates/changeset/changeset.html:72
+#: rhodecode/templates/changelog/changelog.html:89
+#: rhodecode/templates/changeset/changeset.html:38
+#: rhodecode/templates/changeset/changeset_file_comment.html:20
+#: rhodecode/templates/changeset/changeset_range.html:46
+#, fuzzy
+msgid "Changeset status"
+msgstr "Conjuntos de mudanças"
+
+#: rhodecode/templates/changelog/changelog.html:92
+msgid "Click to open associated pull request"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:102
+#: rhodecode/templates/changeset/changeset.html:78
 msgid "Parent"
 msgstr "Progenitor"
 
-#: rhodecode/templates/changelog/changelog.html:77
-#: rhodecode/templates/changeset/changeset.html:77
+#: rhodecode/templates/changelog/changelog.html:108
+#: rhodecode/templates/changeset/changeset.html:84
 msgid "No parents"
 msgstr "Sem progenitores"
 
-#: rhodecode/templates/changelog/changelog.html:82
-#: rhodecode/templates/changeset/changeset.html:80
+#: rhodecode/templates/changelog/changelog.html:113
+#: rhodecode/templates/changeset/changeset.html:88
+msgid "merge"
+msgstr "mesclar"
+
+#: rhodecode/templates/changelog/changelog.html:116
+#: rhodecode/templates/changeset/changeset.html:91
 #: rhodecode/templates/files/files.html:29
-#: rhodecode/templates/files/files_annotate.html:25
+#: rhodecode/templates/files/files_add.html:33
 #: rhodecode/templates/files/files_edit.html:33
 #: rhodecode/templates/shortlog/shortlog_data.html:9
 msgid "branch"
 msgstr "ramo"
 
-#: rhodecode/templates/changelog/changelog.html:86
-#: rhodecode/templates/changeset/changeset.html:83
+#: rhodecode/templates/changelog/changelog.html:122
+msgid "bookmark"
+msgstr "marcador"
+
+#: rhodecode/templates/changelog/changelog.html:128
+#: rhodecode/templates/changeset/changeset.html:96
 msgid "tag"
 msgstr "etiqueta"
 
-#: rhodecode/templates/changelog/changelog.html:122
+#: rhodecode/templates/changelog/changelog.html:164
 msgid "Show selected changes __S -> __E"
 msgstr "Mostrar alterações selecionadas __S -> __E"
 
-#: rhodecode/templates/changelog/changelog.html:172
-#: rhodecode/templates/shortlog/shortlog_data.html:61
+#: rhodecode/templates/changelog/changelog.html:255
 msgid "There are no changes yet"
 msgstr "Ainda não há alteações"
 
-#: rhodecode/templates/changelog/changelog_details.html:2
-#: rhodecode/templates/changeset/changeset.html:55
+#: rhodecode/templates/changelog/changelog_details.html:4
+#: rhodecode/templates/changeset/changeset.html:66
 msgid "removed"
 msgstr "removidos"
 
-#: rhodecode/templates/changelog/changelog_details.html:3
-#: rhodecode/templates/changeset/changeset.html:56
+#: rhodecode/templates/changelog/changelog_details.html:5
+#: rhodecode/templates/changeset/changeset.html:67
 msgid "changed"
 msgstr "alterados"
 
-#: rhodecode/templates/changelog/changelog_details.html:4
-#: rhodecode/templates/changeset/changeset.html:57
+#: rhodecode/templates/changelog/changelog_details.html:6
+#: rhodecode/templates/changeset/changeset.html:68
 msgid "added"
 msgstr "adicionados"
 
-#: rhodecode/templates/changelog/changelog_details.html:6
-#: rhodecode/templates/changelog/changelog_details.html:7
 #: rhodecode/templates/changelog/changelog_details.html:8
-#: rhodecode/templates/changeset/changeset.html:59
-#: rhodecode/templates/changeset/changeset.html:60
-#: rhodecode/templates/changeset/changeset.html:61
+#: rhodecode/templates/changelog/changelog_details.html:9
+#: rhodecode/templates/changelog/changelog_details.html:10
+#: rhodecode/templates/changeset/changeset.html:70
+#: rhodecode/templates/changeset/changeset.html:71
+#: rhodecode/templates/changeset/changeset.html:72
 #, python-format
 msgid "affected %s files"
 msgstr "%s arquivos afetados"
 
 #: rhodecode/templates/changeset/changeset.html:6
+#, fuzzy, python-format
+msgid "%s Changeset"
+msgstr "Conjunto de Mudanças"
+
 #: rhodecode/templates/changeset/changeset.html:14
-#: rhodecode/templates/changeset/changeset.html:31
 msgid "Changeset"
 msgstr "Conjunto de Mudanças"
 
-#: rhodecode/templates/changeset/changeset.html:32
-#: rhodecode/templates/changeset/changeset.html:121
-#: rhodecode/templates/changeset/changeset_range.html:78
-#: rhodecode/templates/files/file_diff.html:32
-#: rhodecode/templates/files/file_diff.html:42
+#: rhodecode/templates/changeset/changeset.html:43
+#: rhodecode/templates/changeset/diff_block.html:20
 msgid "raw diff"
 msgstr "diff bruto"
 
-#: rhodecode/templates/changeset/changeset.html:34
-#: rhodecode/templates/changeset/changeset.html:123
-#: rhodecode/templates/changeset/changeset_range.html:80
-#: rhodecode/templates/files/file_diff.html:34
+#: rhodecode/templates/changeset/changeset.html:44
+#: rhodecode/templates/changeset/diff_block.html:21
 msgid "download diff"
 msgstr "descarregar diff"
 
-#: rhodecode/templates/changeset/changeset.html:90
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d comentário"
+msgstr[1] "%d comentários"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
 #, python-format
-msgid "%s files affected with %s additions and %s deletions."
-msgstr "%s arquivos afetados com %s adições e %s exclusões"
-
-#: rhodecode/templates/changeset/changeset.html:101
+msgid "(%d inline)"
+msgid_plural "(%d inline)"
+msgstr[0] "(%d em linha)"
+msgstr[1] "(%d em linha)"
+
+#: rhodecode/templates/changeset/changeset.html:103
+#, python-format
+msgid "%s files affected with %s insertions and %s deletions:"
+msgstr "%s arquivos afetados com %s inserções e %s exclusões"
+
+#: rhodecode/templates/changeset/changeset.html:119
 msgid "Changeset was too big and was cut off..."
 msgstr "Conjunto de mudanças era grande demais e foi cortado..."
 
-#: rhodecode/templates/changeset/changeset.html:119
-#: rhodecode/templates/changeset/changeset_range.html:76
-#: rhodecode/templates/files/file_diff.html:30
+#: rhodecode/templates/changeset/changeset_file_comment.html:42
+msgid "Submitting..."
+msgstr "Enviando..."
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:45
+msgid "Commenting on line {1}."
+msgstr "Comentando a linha {1}."
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:46
+#: rhodecode/templates/changeset/changeset_file_comment.html:121
+#, python-format
+msgid "Comments parsed using %s syntax with %s support."
+msgstr "Comentários interpretados usando a sintaxe %s com suporte a %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"
+msgstr ""
+"Use @nomedeusuário dentro desse texto para enviar notificação a este "
+"usuário do RhodeCode"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:59
+#: rhodecode/templates/changeset/changeset_file_comment.html:138
+msgid "Comment"
+msgstr "Comentário"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:60
+#: rhodecode/templates/changeset/changeset_file_comment.html:71
+msgid "Hide"
+msgstr "Ocultar"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "You need to be logged in to comment."
+msgstr "Você precisa estar logado para comentar."
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "Login now"
+msgstr "Entrar agora"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:118
+msgid "Leave a comment"
+msgstr "Deixar um comentário"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "Check this to change current status of code-review for this changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+#, fuzzy
+msgid "change status"
+msgstr "Conjuntos de mudanças"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:140
+msgid "Comment and close"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:5
+#, fuzzy, python-format
+msgid "%s Changesets"
+msgstr "Conjuntos de mudanças"
+
+#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/templates/compare/compare_diff.html:29
+msgid "Compare View"
+msgstr "Exibir Comparação"
+
+#: rhodecode/templates/changeset/changeset_range.html:54
+#: rhodecode/templates/compare/compare_diff.html:41
+#: rhodecode/templates/pullrequests/pullrequest_show.html:69
+msgid "Files affected"
+msgstr "Arquivos afetados"
+
+#: rhodecode/templates/changeset/diff_block.html:19
 msgid "diff"
 msgstr "diff"
 
-#: rhodecode/templates/changeset/changeset.html:132
-#: rhodecode/templates/changeset/changeset_range.html:89
-msgid "No changes in this file"
-msgstr "Nenhuma alteração nesse arquivo"
-
-#: rhodecode/templates/changeset/changeset_range.html:30
-msgid "Compare View"
-msgstr "Exibir Comparação"
-
-#: rhodecode/templates/changeset/changeset_range.html:52
-msgid "Files affected"
-msgstr "Arquivos afetados"
-
-#: rhodecode/templates/errors/error_document.html:44
+#: rhodecode/templates/changeset/diff_block.html:27
+msgid "show inline comments"
+msgstr "mostrar comentários em linha"
+
+#: rhodecode/templates/compare/compare_cs.html:5
+#, fuzzy
+msgid "No changesets"
+msgstr "Nenhum conjunto de alterações ainda."
+
+#: rhodecode/templates/compare/compare_diff.html:37
+#, fuzzy
+msgid "Outgoing changesets"
+msgstr "Nenhum conjunto de alterações ainda."
+
+#: rhodecode/templates/data_table/_dt_elements.html:39
+#: rhodecode/templates/data_table/_dt_elements.html:41
+#: rhodecode/templates/data_table/_dt_elements.html:43
+msgid "Fork"
+msgstr "Bifurcação"
+
+#: rhodecode/templates/data_table/_dt_elements.html:60
+#: rhodecode/templates/journal/journal.html:126
+#: rhodecode/templates/summary/summary.html:68
+msgid "Mercurial repository"
+msgstr "Repositório Mercurial"
+
+#: rhodecode/templates/data_table/_dt_elements.html:62
+#: rhodecode/templates/journal/journal.html:128
+#: rhodecode/templates/summary/summary.html:71
+msgid "Git repository"
+msgstr "Repositório Git"
+
+#: rhodecode/templates/data_table/_dt_elements.html:69
+#: rhodecode/templates/journal/journal.html:134
+#: rhodecode/templates/summary/summary.html:78
+msgid "public repository"
+msgstr "repositório público"
+
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/summary/summary.html:87
+#: rhodecode/templates/summary/summary.html:88
+msgid "Fork of"
+msgstr "Bifurcação de"
+
+#: rhodecode/templates/data_table/_dt_elements.html:92
+msgid "No changesets yet"
+msgstr "Nenhum conjunto de alterações ainda."
+
+#: rhodecode/templates/data_table/_dt_elements.html:104
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr "Confirma excluir este usuário: %s"
+
+#: rhodecode/templates/email_templates/main.html:8
+msgid "This is an notification from RhodeCode."
+msgstr "Esta é uma notificação do RhodeCode."
+
+#: rhodecode/templates/errors/error_document.html:46
 #, python-format
 msgid "You will be redirected to %s in %s seconds"
 msgstr "Você será redirecionado para %s em %s segundos"
 
 #: rhodecode/templates/files/file_diff.html:4
+#, fuzzy, python-format
+msgid "%s File diff"
+msgstr "Diff do arquivo"
+
 #: rhodecode/templates/files/file_diff.html:12
 msgid "File diff"
 msgstr "Diff do arquivo"
 
-#: rhodecode/templates/files/file_diff.html:42
-msgid "Diff is to big to display"
-msgstr "Diff é grande demais para exibir"
-
-#: rhodecode/templates/files/files.html:37
-#: rhodecode/templates/files/files_annotate.html:31
+#: rhodecode/templates/files/files.html:4
+#: rhodecode/templates/files/files.html:72
+#, fuzzy, python-format
+msgid "%s files"
+msgstr "arquivos"
+
+#: rhodecode/templates/files/files.html:12
+#: rhodecode/templates/summary/summary.html:340
+msgid "files"
+msgstr "arquivos"
+
+#: rhodecode/templates/files/files_add.html:4
+#: rhodecode/templates/files/files_edit.html:4
+#, fuzzy, python-format
+msgid "%s Edit file"
+msgstr "editar arquivo"
+
+#: rhodecode/templates/files/files_add.html:19
+msgid "add file"
+msgstr "adicionar arquivo"
+
+#: rhodecode/templates/files/files_add.html:40
+msgid "Add new file"
+msgstr "Adicionar novo arquivo"
+
+#: rhodecode/templates/files/files_add.html:45
+msgid "File Name"
+msgstr "Nome de Arquivo"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:58
+msgid "or"
+msgstr "ou"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:54
+msgid "Upload file"
+msgstr "Enviar arquivo"
+
+#: rhodecode/templates/files/files_add.html:58
+msgid "Create new file"
+msgstr "Criar novo arquivo"
+
+#: rhodecode/templates/files/files_add.html:63
 #: rhodecode/templates/files/files_edit.html:39
+#: rhodecode/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "Local"
 
-#: rhodecode/templates/files/files.html:46
-msgid "Go back"
-msgstr "Voltar"
-
-#: rhodecode/templates/files/files.html:47
-msgid "No files at given path"
-msgstr "Nenhum arquivo no caminho especificado"
-
-#: rhodecode/templates/files/files_annotate.html:4
-msgid "File annotate"
-msgstr "Anotar arquivo"
-
-#: rhodecode/templates/files/files_annotate.html:12
-msgid "annotate"
-msgstr "anotar"
-
-#: rhodecode/templates/files/files_annotate.html:33
-#: rhodecode/templates/files/files_browser.html:160
-#: rhodecode/templates/files/files_source.html:2
-msgid "Revision"
-msgstr "Revisão"
-
-#: rhodecode/templates/files/files_annotate.html:36
-#: rhodecode/templates/files/files_browser.html:158
-#: rhodecode/templates/files/files_source.html:7
-msgid "Size"
-msgstr "Tamanho"
-
-#: rhodecode/templates/files/files_annotate.html:38
-#: rhodecode/templates/files/files_browser.html:159
-#: rhodecode/templates/files/files_source.html:9
-msgid "Mimetype"
-msgstr "Mimetype"
-
-#: rhodecode/templates/files/files_annotate.html:41
-msgid "show source"
-msgstr "mostrar fonte"
-
-#: rhodecode/templates/files/files_annotate.html:43
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:14
-#: rhodecode/templates/files/files_source.html:51
-msgid "show as raw"
-msgstr "mostrar como bruto"
-
-#: rhodecode/templates/files/files_annotate.html:45
-#: rhodecode/templates/files/files_source.html:16
-msgid "download as raw"
-msgstr "descarregar como bruto"
-
-#: rhodecode/templates/files/files_annotate.html:54
-#: rhodecode/templates/files/files_source.html:25
-msgid "History"
-msgstr "Histórico"
-
-#: rhodecode/templates/files/files_annotate.html:73
-#: rhodecode/templates/files/files_source.html:46
-#, python-format
-msgid "Binary file (%s)"
-msgstr "Arquivo binário (%s)"
-
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:51
-msgid "File is too big to display"
-msgstr "Arquivo é grande demais para exibir"
+#: rhodecode/templates/files/files_add.html:67
+msgid "use / to separate directories"
+msgstr "use / para separar diretórios"
+
+#: rhodecode/templates/files/files_add.html:77
+#: rhodecode/templates/files/files_edit.html:63
+#: rhodecode/templates/shortlog/shortlog_data.html:6
+msgid "commit message"
+msgstr "mensagem de commit"
+
+#: rhodecode/templates/files/files_add.html:81
+#: rhodecode/templates/files/files_edit.html:67
+msgid "Commit changes"
+msgstr "Realizar commit das alterações"
 
 #: rhodecode/templates/files/files_browser.html:13
 msgid "view"
@@ -2286,59 +3465,165 @@ msgstr "seguir ramo atual"
 msgid "search file list"
 msgstr "pesquisar lista de arquivos"
 
-#: rhodecode/templates/files/files_browser.html:32
+#: rhodecode/templates/files/files_browser.html:31
+#: rhodecode/templates/shortlog/shortlog_data.html:65
+msgid "add new file"
+msgstr "adicionar novo arquivo"
+
+#: rhodecode/templates/files/files_browser.html:35
 msgid "Loading file list..."
 msgstr "Carregando lista de arquivos..."
 
-#: rhodecode/templates/files/files_browser.html:111
-msgid "search truncated"
-msgstr "pesquisa truncada"
-
-#: rhodecode/templates/files/files_browser.html:122
-msgid "no matching files"
-msgstr "nenhum arquivo corresponde"
-
-#: rhodecode/templates/files/files_browser.html:161
+#: rhodecode/templates/files/files_browser.html:48
+msgid "Size"
+msgstr "Tamanho"
+
+#: rhodecode/templates/files/files_browser.html:49
+msgid "Mimetype"
+msgstr "Mimetype"
+
+#: rhodecode/templates/files/files_browser.html:50
+msgid "Last Revision"
+msgstr "Última revisão"
+
+#: rhodecode/templates/files/files_browser.html:51
 msgid "Last modified"
 msgstr "Última alteração"
 
-#: rhodecode/templates/files/files_browser.html:162
+#: rhodecode/templates/files/files_browser.html:52
 msgid "Last commiter"
 msgstr "Último commiter"
 
-#: rhodecode/templates/files/files_edit.html:4
-msgid "Edit file"
-msgstr "Editar arquivo"
-
 #: rhodecode/templates/files/files_edit.html:19
 msgid "edit file"
 msgstr "editar arquivo"
 
-#: rhodecode/templates/files/files_edit.html:45
-#: rhodecode/templates/shortlog/shortlog_data.html:5
-msgid "commit message"
-msgstr "mensagem de commit"
+#: rhodecode/templates/files/files_edit.html:49
+#: rhodecode/templates/files/files_source.html:38
+msgid "show annotation"
+msgstr "mostrar anotação"
+
+#: rhodecode/templates/files/files_edit.html:50
+#: rhodecode/templates/files/files_source.html:40
+#: rhodecode/templates/files/files_source.html:68
+msgid "show as raw"
+msgstr "mostrar como bruto"
 
 #: rhodecode/templates/files/files_edit.html:51
-msgid "Commit changes"
-msgstr "Realizar commit das alterações"
-
-#: rhodecode/templates/files/files_source.html:12
-msgid "show annotation"
-msgstr "mostrar anotação"
-
-#: rhodecode/templates/files/files_source.html:153
+#: rhodecode/templates/files/files_source.html:41
+msgid "download as raw"
+msgstr "descarregar como bruto"
+
+#: rhodecode/templates/files/files_edit.html:54
+msgid "source"
+msgstr "fonte"
+
+#: rhodecode/templates/files/files_edit.html:59
+msgid "Editing file"
+msgstr "Editando arquivo"
+
+#: rhodecode/templates/files/files_source.html:2
+msgid "History"
+msgstr "Histórico"
+
+#: rhodecode/templates/files/files_source.html:9
+#, fuzzy
+msgid "diff to revision"
+msgstr "próxima revisão"
+
+#: rhodecode/templates/files/files_source.html:10
+#, fuzzy
+msgid "show at revision"
+msgstr "próxima revisão"
+
+#: rhodecode/templates/files/files_source.html:14
+#, fuzzy, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] "autor"
+msgstr[1] "autors"
+
+#: rhodecode/templates/files/files_source.html:36
+msgid "show source"
+msgstr "mostrar fonte"
+
+#: rhodecode/templates/files/files_source.html:59
+#, python-format
+msgid "Binary file (%s)"
+msgstr "Arquivo binário (%s)"
+
+#: rhodecode/templates/files/files_source.html:68
+msgid "File is too big to display"
+msgstr "Arquivo é grande demais para exibir"
+
+#: rhodecode/templates/files/files_source.html:124
 msgid "Selection link"
 msgstr "Link da seleção"
 
+#: rhodecode/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr "anotação"
+
+#: rhodecode/templates/files/files_ypjax.html:15
+msgid "Go back"
+msgstr "Voltar"
+
+#: rhodecode/templates/files/files_ypjax.html:16
+msgid "No files at given path"
+msgstr "Nenhum arquivo no caminho especificado"
+
+#: rhodecode/templates/followers/followers.html:5
+#, fuzzy, python-format
+msgid "%s Followers"
+msgstr "seguidores"
+
 #: rhodecode/templates/followers/followers.html:13
 msgid "followers"
 msgstr "seguidores"
 
 #: rhodecode/templates/followers/followers_data.html:12
-msgid "Started following"
+#, fuzzy
+msgid "Started following -"
 msgstr "Passou a seguir"
 
+#: rhodecode/templates/forks/fork.html:5
+#, fuzzy, python-format
+msgid "%s Fork"
+msgstr "bifurcação"
+
+#: rhodecode/templates/forks/fork.html:31
+msgid "Fork name"
+msgstr "Nome da bifurcação"
+
+#: rhodecode/templates/forks/fork.html:68
+msgid "Private"
+msgstr "Privado"
+
+#: rhodecode/templates/forks/fork.html:77
+msgid "Copy permissions"
+msgstr "Copiar permissões"
+
+#: rhodecode/templates/forks/fork.html:81
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:86
+msgid "Update after clone"
+msgstr "Atualizar após clonar"
+
+#: rhodecode/templates/forks/fork.html:90
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:94
+msgid "fork this repository"
+msgstr "bifurcar este repositório"
+
+#: rhodecode/templates/forks/forks.html:5
+#, fuzzy, python-format
+msgid "%s Forks"
+msgstr "bifurcações"
+
 #: rhodecode/templates/forks/forks.html:13
 msgid "forks"
 msgstr "bifurcações"
@@ -2347,184 +3632,413 @@ msgstr "bifurcações"
 msgid "forked"
 msgstr "bifurcado"
 
-#: rhodecode/templates/forks/forks_data.html:34
+#: rhodecode/templates/forks/forks_data.html:38
 msgid "There are no forks yet"
 msgstr "Ainda não há bifurcações"
 
-#: rhodecode/templates/journal/journal.html:34
-msgid "Following"
-msgstr "Seguindo"
-
-#: rhodecode/templates/journal/journal.html:41
-msgid "following user"
-msgstr "seguindo usuário"
+#: rhodecode/templates/journal/journal.html:13
+#, fuzzy
+msgid "ATOM journal feed"
+msgstr "diário público de %s - feed %s"
+
+#: rhodecode/templates/journal/journal.html:14
+#, fuzzy
+msgid "RSS journal feed"
+msgstr "diário público de %s - feed %s"
+
+#: rhodecode/templates/journal/journal.html:24
+#: rhodecode/templates/pullrequests/pullrequest.html:27
+msgid "Refresh"
+msgstr "Atualizar"
+
+#: rhodecode/templates/journal/journal.html:27
+#: rhodecode/templates/journal/public_journal.html:24
+#, fuzzy
+msgid "RSS feed"
+msgstr "%s - feed %s"
+
+#: rhodecode/templates/journal/journal.html:30
+#: rhodecode/templates/journal/public_journal.html:27
+msgid "ATOM feed"
+msgstr ""
 
 #: rhodecode/templates/journal/journal.html:41
+msgid "Watched"
+msgstr "Seguindo"
+
+#: rhodecode/templates/journal/journal.html:46
+msgid "ADD"
+msgstr "ADICIONAR"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "following user"
+msgstr "seguindo usuário"
+
+#: rhodecode/templates/journal/journal.html:114
 msgid "user"
 msgstr "usuário"
 
-#: rhodecode/templates/journal/journal.html:65
+#: rhodecode/templates/journal/journal.html:147
 msgid "You are not following any users or repositories"
 msgstr "Você não está seguindo quaisquer usuários ou repositórios"
 
-#: rhodecode/templates/journal/journal_data.html:46
+#: rhodecode/templates/journal/journal_data.html:47
 msgid "No entries yet"
 msgstr "Ainda não há entradas"
 
-#: rhodecode/templates/journal/public_journal.html:17
+#: rhodecode/templates/journal/public_journal.html:13
+#, fuzzy
+msgid "ATOM public journal feed"
+msgstr "diário público de %s - feed %s"
+
+#: rhodecode/templates/journal/public_journal.html:14
+#, fuzzy
+msgid "RSS public journal feed"
+msgstr "diário público de %s - feed %s"
+
+#: rhodecode/templates/journal/public_journal.html:21
 msgid "Public Journal"
 msgstr "Diário Público"
 
-#: rhodecode/templates/search/search.html:7
-#: rhodecode/templates/search/search.html:26
-msgid "in repository: "
+#: rhodecode/templates/pullrequests/pullrequest.html:4
+#: rhodecode/templates/pullrequests/pullrequest.html:12
+msgid "New pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:28
+msgid "refresh overview"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:66
+#, fuzzy
+msgid "Detailed compare view"
+msgstr "comparar exibir"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:70
+#: rhodecode/templates/pullrequests/pullrequest_show.html:82
+msgid "Pull request reviewers"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:79
+#: rhodecode/templates/pullrequests/pullrequest_show.html:94
+#, fuzzy
+msgid "owner"
+msgstr "Dono"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:91
+#: rhodecode/templates/pullrequests/pullrequest_show.html:109
+msgid "Add reviewer to this pull request."
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:97
+#, fuzzy
+msgid "Create new pull request"
+msgstr "Criar novo arquivo"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:106
+#: rhodecode/templates/pullrequests/pullrequest_show.html:25
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
+#, fuzzy
+msgid "Title"
+msgstr "escrever"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:115
+#, fuzzy
+msgid "description"
+msgstr "Descrição"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:123
+msgid "Send pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:23
+#, python-format
+msgid "Closed %s"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:31
+#, fuzzy
+msgid "Status"
+msgstr "Conjuntos de mudanças"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:36
+msgid "Pull request status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:44
+msgid "Still not reviewed by"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:47
+#, python-format
+msgid "%d reviewer"
+msgid_plural "%d reviewers"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:54
+#, fuzzy
+msgid "Created on"
+msgstr "criar um agora"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:61
+#, fuzzy
+msgid "Compare view"
+msgstr "comparar exibir"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:65
+#, fuzzy
+msgid "Incoming changesets"
+msgstr "Nenhum conjunto de alterações ainda."
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
+#, fuzzy
+msgid "all pull requests"
+msgstr "Criar novo arquivo"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
+msgid "All pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
+msgid "Closed"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:6
+#, fuzzy, python-format
+msgid "Search \"%s\" in repository: %s"
 msgstr "no repositório"
 
-#: rhodecode/templates/search/search.html:9
-#: rhodecode/templates/search/search.html:28
-msgid "in all repositories"
+#: rhodecode/templates/search/search.html:8
+#, fuzzy, python-format
+msgid "Search \"%s\" in all repositories"
 msgstr "em todos os repositórios"
 
-#: rhodecode/templates/search/search.html:42
+#: rhodecode/templates/search/search.html:12
+#: rhodecode/templates/search/search.html:32
+#, fuzzy, python-format
+msgid "Search in repository: %s"
+msgstr "no repositório"
+
+#: rhodecode/templates/search/search.html:14
+#: rhodecode/templates/search/search.html:34
+#, fuzzy
+msgid "Search in all repositories"
+msgstr "em todos os repositórios"
+
+#: rhodecode/templates/search/search.html:48
 msgid "Search term"
 msgstr "Termo de pesquisa"
 
-#: rhodecode/templates/search/search.html:54
+#: rhodecode/templates/search/search.html:60
 msgid "Search in"
 msgstr "Pesquisando em"
 
-#: rhodecode/templates/search/search.html:57
+#: rhodecode/templates/search/search.html:63
 msgid "File contents"
 msgstr "Conteúdo dos arquivos"
 
-#: rhodecode/templates/search/search.html:59
+#: rhodecode/templates/search/search.html:64
+#, fuzzy
+msgid "Commit messages"
+msgstr "mensagem de commit"
+
+#: rhodecode/templates/search/search.html:65
 msgid "File names"
 msgstr "Nomes dos arquivos"
 
-#: rhodecode/templates/search/search_content.html:20
+#: rhodecode/templates/search/search_commit.html:35
+#: rhodecode/templates/search/search_content.html:21
 #: rhodecode/templates/search/search_path.html:15
 msgid "Permission denied"
 msgstr "Permissão negada"
 
-#: rhodecode/templates/settings/repo_fork.html:5
-msgid "Fork"
-msgstr "Bifurcação"
-
-#: rhodecode/templates/settings/repo_fork.html:31
-msgid "Fork name"
-msgstr "Nome da bifurcação"
-
-#: rhodecode/templates/settings/repo_fork.html:55
-msgid "fork this repository"
-msgstr "bifurcar este repositório"
+#: rhodecode/templates/settings/repo_settings.html:5
+#, fuzzy, python-format
+msgid "%s Settings"
+msgstr "configurações"
 
 #: rhodecode/templates/shortlog/shortlog.html:5
-#: rhodecode/templates/summary/summary.html:666
-msgid "Shortlog"
-msgstr "Log resumido"
+#, fuzzy, python-format
+msgid "%s Shortlog"
+msgstr "log resumido"
 
 #: rhodecode/templates/shortlog/shortlog.html:14
 msgid "shortlog"
 msgstr "log resumido"
 
-#: rhodecode/templates/shortlog/shortlog_data.html:6
+#: rhodecode/templates/shortlog/shortlog_data.html:7
 msgid "age"
 msgstr "idade"
 
+#: rhodecode/templates/shortlog/shortlog_data.html:18
+msgid "No commit message"
+msgstr "Nenhuma mensagem de commit"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:62
+msgid "Add or upload files directly via RhodeCode"
+msgstr "Adicionar ou enviar arquivos diretamente pelo RhodeCode"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:71
+msgid "Push new repo"
+msgstr "Fazer push de novo repositório"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:79
+msgid "Existing repository?"
+msgstr "Repositório existente?"
+
+#: rhodecode/templates/summary/summary.html:4
+#, fuzzy, python-format
+msgid "%s Summary"
+msgstr "sumário"
+
 #: rhodecode/templates/summary/summary.html:12
 msgid "summary"
 msgstr "sumário"
 
-#: rhodecode/templates/summary/summary.html:79
+#: rhodecode/templates/summary/summary.html:20
+#, fuzzy, python-format
+msgid "repo %s ATOM feed"
+msgstr "Assinar o feed atom de %s"
+
+#: rhodecode/templates/summary/summary.html:21
+#, fuzzy, python-format
+msgid "repo %s RSS feed"
+msgstr "Assinar o feed rss de %s"
+
+#: rhodecode/templates/summary/summary.html:49
+#: rhodecode/templates/summary/summary.html:52
+msgid "ATOM"
+msgstr "ATOM"
+
+#: rhodecode/templates/summary/summary.html:82
+#, python-format
+msgid "Non changable ID %s"
+msgstr "ID não alterável %s"
+
+#: rhodecode/templates/summary/summary.html:87
+msgid "public"
+msgstr "público"
+
+#: rhodecode/templates/summary/summary.html:95
 msgid "remote clone"
 msgstr "clone remoto"
 
-#: rhodecode/templates/summary/summary.html:121
-msgid "by"
-msgstr "por"
-
-#: rhodecode/templates/summary/summary.html:128
+#: rhodecode/templates/summary/summary.html:116
+msgid "Contact"
+msgstr "Contato"
+
+#: rhodecode/templates/summary/summary.html:130
 msgid "Clone url"
 msgstr "URL de clonagem"
 
-#: rhodecode/templates/summary/summary.html:137
-msgid "Trending source files"
-msgstr "Tendências nos arquivos fonte"
-
-#: rhodecode/templates/summary/summary.html:146
+#: rhodecode/templates/summary/summary.html:133
+msgid "Show by Name"
+msgstr "Mostrar por Nome"
+
+#: rhodecode/templates/summary/summary.html:134
+msgid "Show by ID"
+msgstr "Mostrar por ID"
+
+#: rhodecode/templates/summary/summary.html:142
+msgid "Trending files"
+msgstr "Tendências em arquivos"
+
+#: rhodecode/templates/summary/summary.html:150
+#: rhodecode/templates/summary/summary.html:166
+#: rhodecode/templates/summary/summary.html:194
+msgid "enable"
+msgstr "habilitar"
+
+#: rhodecode/templates/summary/summary.html:158
 msgid "Download"
 msgstr "Download"
 
-#: rhodecode/templates/summary/summary.html:150
+#: rhodecode/templates/summary/summary.html:162
 msgid "There are no downloads yet"
 msgstr "Ainda não há downloads"
 
-#: rhodecode/templates/summary/summary.html:152
+#: rhodecode/templates/summary/summary.html:164
 msgid "Downloads are disabled for this repository"
 msgstr "Downloads estão desabilitados para este repositório"
 
-#: rhodecode/templates/summary/summary.html:154
-#: rhodecode/templates/summary/summary.html:320
-msgid "enable"
-msgstr "habilitar"
-
-#: rhodecode/templates/summary/summary.html:162
-#: rhodecode/templates/summary/summary.html:297
+#: rhodecode/templates/summary/summary.html:170
+#, fuzzy
+msgid "Download as zip"
+msgstr "descarregar como bruto"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "Check this to download archive with subrepos"
+msgstr "Marque isto para descarregar arquivo com subrepositórios"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "with subrepos"
+msgstr "com subrepositórios"
+
+#: rhodecode/templates/summary/summary.html:186
+msgid "Commit activity by day / author"
+msgstr "Atividade de commit por dia / autor"
+
+#: rhodecode/templates/summary/summary.html:197
+msgid "Stats gathered: "
+msgstr "Estatísticas coletadas:"
+
+#: rhodecode/templates/summary/summary.html:218
+msgid "Shortlog"
+msgstr "Log resumido"
+
+#: rhodecode/templates/summary/summary.html:220
+msgid "Quick start"
+msgstr "Início rápido"
+
+#: rhodecode/templates/summary/summary.html:233
+#, python-format
+msgid "Readme file at revision '%s'"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:236
+msgid "Permalink to this readme"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:293
 #, python-format
 msgid "Download %s as %s"
 msgstr "Descarregar %s como %s"
 
-#: rhodecode/templates/summary/summary.html:168
-msgid "Check this to download archive with subrepos"
-msgstr "Marque isto para descarregar arquivo com subrepositórios"
-
-#: rhodecode/templates/summary/summary.html:168
-msgid "with subrepos"
-msgstr "com subrepositórios"
-
-#: rhodecode/templates/summary/summary.html:176
-msgid "Feeds"
-msgstr "Feeds"
-
-#: rhodecode/templates/summary/summary.html:257
-#: rhodecode/templates/summary/summary.html:684
-#: rhodecode/templates/summary/summary.html:695
-msgid "show more"
-msgstr "mostrar mais"
-
-#: rhodecode/templates/summary/summary.html:312
-msgid "Commit activity by day / author"
-msgstr "Atividade de commit por dia / autor"
-
-#: rhodecode/templates/summary/summary.html:324
-msgid "Loaded in"
-msgstr "Carregado em"
-
-#: rhodecode/templates/summary/summary.html:603
+#: rhodecode/templates/summary/summary.html:650
 msgid "commits"
 msgstr "commits"
 
-#: rhodecode/templates/summary/summary.html:604
+#: rhodecode/templates/summary/summary.html:651
 msgid "files added"
 msgstr "arquivos adicionados"
 
-#: rhodecode/templates/summary/summary.html:605
+#: rhodecode/templates/summary/summary.html:652
 msgid "files changed"
 msgstr "arquivos alterados"
 
-#: rhodecode/templates/summary/summary.html:606
+#: rhodecode/templates/summary/summary.html:653
 msgid "files removed"
 msgstr "arquivos removidos"
 
-#: rhodecode/templates/summary/summary.html:610
+#: rhodecode/templates/summary/summary.html:656
+msgid "commit"
+msgstr "commit"
+
+#: rhodecode/templates/summary/summary.html:657
 msgid "file added"
 msgstr "arquivo adicionado"
 
-#: rhodecode/templates/summary/summary.html:611
+#: rhodecode/templates/summary/summary.html:658
 msgid "file changed"
 msgstr "arquivo alterado"
 
-#: rhodecode/templates/summary/summary.html:612
+#: rhodecode/templates/summary/summary.html:659
 msgid "file removed"
 msgstr "arquivo removido"
 
+#: rhodecode/templates/tags/tags.html:5
+#, fuzzy, python-format
+msgid "%s Tags"
+msgstr "%s atrás"
+
diff --git a/rhodecode/i18n/rhodecode.pot b/rhodecode/i18n/rhodecode.pot
--- a/rhodecode/i18n/rhodecode.pot
+++ b/rhodecode/i18n/rhodecode.pot
@@ -1,14 +1,14 @@
 # Translations template for RhodeCode.
-# Copyright (C) 2011 ORGANIZATION
+# Copyright (C) 2012 ORGANIZATION
 # This file is distributed under the same license as the RhodeCode project.
-# FIRST AUTHOR , 2011.
+# FIRST AUTHOR , 2012.
 #
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: RhodeCode 1.2.0\n"
+"Project-Id-Version: RhodeCode 1.4.0b\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-09-14 15:50-0300\n"
+"POT-Creation-Date: 2012-09-02 20:30+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME \n"
 "Language-Team: LANGUAGE \n"
@@ -17,17 +17,36 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Generated-By: Babel 0.9.6\n"
 
-#: rhodecode/controllers/changeset.py:108 rhodecode/controllers/changeset.py:149
-#: rhodecode/controllers/changeset.py:216 rhodecode/controllers/changeset.py:229
+#: rhodecode/controllers/changelog.py:94
+msgid "All Branches"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:83
+msgid "show white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+msgid "ignore white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:157
+#, python-format
+msgid "%s line context"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:333 rhodecode/controllers/changeset.py:348
+#: rhodecode/lib/diffs.py:70
 msgid "binary file"
 msgstr ""
 
-#: rhodecode/controllers/changeset.py:123 rhodecode/controllers/changeset.py:168
-msgid "Changeset is to big and was cut off, see raw changeset instead"
-msgstr ""
-
-#: rhodecode/controllers/changeset.py:159
-msgid "Diff is to big and was cut off, see raw diff instead"
+#: rhodecode/controllers/changeset.py:408
+msgid ""
+"Changing status on a changeset associated witha closed pull request is not "
+"allowed"
+msgstr ""
+
+#: rhodecode/controllers/compare.py:69
+msgid "There are no changesets yet"
 msgstr ""
 
 #: rhodecode/controllers/error.py:69
@@ -56,240 +75,290 @@ msgid ""
 "fulfilling the request."
 msgstr ""
 
-#: rhodecode/controllers/feed.py:48
+#: rhodecode/controllers/feed.py:49
 #, python-format
 msgid "Changes on %s repository"
 msgstr ""
 
-#: rhodecode/controllers/feed.py:49
+#: rhodecode/controllers/feed.py:50
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: rhodecode/controllers/files.py:72
-msgid "There are no files yet"
-msgstr ""
-
-#: rhodecode/controllers/files.py:262
+#: rhodecode/controllers/feed.py:75
+msgid "commited on"
+msgstr ""
+
+#: rhodecode/controllers/files.py:84
+msgid "click here to add new file"
+msgstr ""
+
+#: rhodecode/controllers/files.py:85
+#, python-format
+msgid "There are no files yet %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
+#, python-format
+msgid "This repository is has been locked by %s on %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:266
 #, python-format
 msgid "Edited %s via RhodeCode"
 msgstr ""
 
-#: rhodecode/controllers/files.py:267 rhodecode/templates/files/file_diff.html:40
+#: rhodecode/controllers/files.py:271
 msgid "No changes"
 msgstr ""
 
-#: rhodecode/controllers/files.py:278
+#: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
 #, python-format
 msgid "Successfully committed to %s"
 msgstr ""
 
-#: rhodecode/controllers/files.py:283
+#: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
 msgid "Error occurred during commit"
 msgstr ""
 
-#: rhodecode/controllers/files.py:308
+#: rhodecode/controllers/files.py:318
+#, python-format
+msgid "Added %s via RhodeCode"
+msgstr ""
+
+#: rhodecode/controllers/files.py:332
+msgid "No content"
+msgstr ""
+
+#: rhodecode/controllers/files.py:336
+msgid "No filename"
+msgstr ""
+
+#: rhodecode/controllers/files.py:378
 msgid "downloads disabled"
 msgstr ""
 
-#: rhodecode/controllers/files.py:313
+#: rhodecode/controllers/files.py:389
 #, python-format
 msgid "Unknown revision %s"
 msgstr ""
 
-#: rhodecode/controllers/files.py:315
+#: rhodecode/controllers/files.py:391
 msgid "Empty repository"
 msgstr ""
 
-#: rhodecode/controllers/files.py:317
+#: rhodecode/controllers/files.py:393
 msgid "Unknown archive type"
 msgstr ""
 
-#: rhodecode/controllers/files.py:385 rhodecode/controllers/files.py:398
-msgid "Binary file"
-msgstr ""
-
-#: rhodecode/controllers/files.py:417
-#: rhodecode/templates/changeset/changeset_range.html:4
-#: rhodecode/templates/changeset/changeset_range.html:12
-#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/controllers/files.py:494
+#: rhodecode/templates/changeset/changeset_range.html:13
+#: rhodecode/templates/changeset/changeset_range.html:31
 msgid "Changesets"
 msgstr ""
 
-#: rhodecode/controllers/files.py:418 rhodecode/controllers/summary.py:175
-#: rhodecode/templates/branches/branches.html:5
-#: rhodecode/templates/summary/summary.html:690
+#: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
+#: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
 msgid "Branches"
 msgstr ""
 
-#: rhodecode/controllers/files.py:419 rhodecode/controllers/summary.py:176
-#: rhodecode/templates/summary/summary.html:679
-#: rhodecode/templates/tags/tags.html:5
+#: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
+#: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
 msgid "Tags"
 msgstr ""
 
-#: rhodecode/controllers/journal.py:50
+#: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
 #, python-format
-msgid "%s public journal %s feed"
-msgstr ""
-
-#: rhodecode/controllers/journal.py:178 rhodecode/controllers/journal.py:212
-#: rhodecode/templates/admin/repos/repo_edit.html:171
-#: rhodecode/templates/base/base.html:50
-msgid "Public journal"
-msgstr ""
-
-#: rhodecode/controllers/login.py:111
-msgid "You have successfully registered into rhodecode"
-msgstr ""
-
-#: rhodecode/controllers/login.py:133
-msgid "Your password reset link was sent"
-msgstr ""
-
-#: rhodecode/controllers/login.py:155
-msgid "Your password reset was successful, new password has been sent to your email"
-msgstr ""
-
-#: rhodecode/controllers/search.py:109
-msgid "Invalid search query. Try quoting it."
-msgstr ""
-
-#: rhodecode/controllers/search.py:114
-msgid "There is no index to search in. Please run whoosh indexer"
-msgstr ""
-
-#: rhodecode/controllers/search.py:118
-msgid "An error occurred during this search operation"
-msgstr ""
-
-#: rhodecode/controllers/settings.py:61 rhodecode/controllers/settings.py:171
+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 ""
+
+#: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
 #, python-format
 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 ""
 
-#: rhodecode/controllers/settings.py:109 rhodecode/controllers/admin/repos.py:239
+#: rhodecode/controllers/forks.py:167
+#, python-format
+msgid "forked %s repository as %s"
+msgstr ""
+
+#: rhodecode/controllers/forks.py:181
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr ""
+
+#: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
+msgid "public journal"
+msgstr ""
+
+#: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
+#: rhodecode/templates/base/base.html:220
+msgid "journal"
+msgstr ""
+
+#: rhodecode/controllers/login.py:143
+msgid "You have successfully registered into rhodecode"
+msgstr ""
+
+#: rhodecode/controllers/login.py:164
+msgid "Your password reset link was sent"
+msgstr ""
+
+#: rhodecode/controllers/login.py:184
+msgid "Your password reset was successful, new password has been sent to your email"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
+msgid "Bookmarks"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:158
+msgid "Pull request requires a title with min. 3 chars"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:160
+msgid "error during creation of pull request"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:181
+msgid "Successfully opened new pull request"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:184
+msgid "Error occurred during sending pull request"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:217
+msgid "Successfully deleted pull request"
+msgstr ""
+
+#: rhodecode/controllers/search.py:131
+msgid "Invalid search query. Try quoting it."
+msgstr ""
+
+#: rhodecode/controllers/search.py:136
+msgid "There is no index to search in. Please run whoosh indexer"
+msgstr ""
+
+#: rhodecode/controllers/search.py:140
+msgid "An error occurred during this search operation"
+msgstr ""
+
+#: rhodecode/controllers/settings.py:107 rhodecode/controllers/admin/repos.py:266
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:126 rhodecode/controllers/admin/repos.py:257
+#: rhodecode/controllers/settings.py:125 rhodecode/controllers/admin/repos.py:284
 #, python-format
 msgid "error occurred during update of repository %s"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:144 rhodecode/controllers/admin/repos.py:275
+#: rhodecode/controllers/settings.py:143 rhodecode/controllers/admin/repos.py:302
 #, python-format
 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 ""
 
-#: rhodecode/controllers/settings.py:156 rhodecode/controllers/admin/repos.py:287
+#: rhodecode/controllers/settings.py:155 rhodecode/controllers/admin/repos.py:314
 #, python-format
 msgid "deleted repository %s"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:159 rhodecode/controllers/admin/repos.py:297
-#: rhodecode/controllers/admin/repos.py:303
+#: rhodecode/controllers/settings.py:159 rhodecode/controllers/admin/repos.py:324
+#: rhodecode/controllers/admin/repos.py:330
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:193
-#, python-format
-msgid "forked %s repository as %s"
-msgstr ""
-
-#: rhodecode/controllers/settings.py:211
-#, python-format
-msgid "An error occurred during repository forking %s"
-msgstr ""
-
-#: rhodecode/controllers/summary.py:123
+#: rhodecode/controllers/summary.py:138
 msgid "No data loaded yet"
 msgstr ""
 
-#: rhodecode/controllers/summary.py:126
+#: rhodecode/controllers/summary.py:142
+#: rhodecode/templates/summary/summary.html:148
 msgid "Statistics are disabled for this repository"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:49
-msgid "BASE"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:50
-msgid "ONELEVEL"
+msgid "BASE"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:51
+msgid "ONELEVEL"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:52
 msgid "SUBTREE"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:55
-msgid "NEVER"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:56
-msgid "ALLOW"
+msgid "NEVER"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:57
-msgid "TRY"
+msgid "ALLOW"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:58
-msgid "DEMAND"
+msgid "TRY"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:59
+msgid "DEMAND"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:60
 msgid "HARD"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:63
-msgid "No encryption"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:64
-msgid "LDAPS connection"
+msgid "No encryption"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:65
+msgid "LDAPS connection"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:66
 msgid "START_TLS on LDAP connection"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:115
+#: rhodecode/controllers/admin/ldap_settings.py:126
 msgid "Ldap settings updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:120
+#: rhodecode/controllers/admin/ldap_settings.py:130
 msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:134
+#: rhodecode/controllers/admin/ldap_settings.py:147
 msgid "error occurred during update of ldap settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:56
-msgid "None"
-msgstr ""
-
-#: rhodecode/controllers/admin/permissions.py:57
-msgid "Read"
-msgstr ""
-
-#: rhodecode/controllers/admin/permissions.py:58
-msgid "Write"
-msgstr ""
-
 #: rhodecode/controllers/admin/permissions.py:59
+msgid "None"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:60
+msgid "Read"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:61
+msgid "Write"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:62
 #: rhodecode/templates/admin/ldap/ldap.html:9
 #: rhodecode/templates/admin/permissions/permissions.html:9
 #: rhodecode/templates/admin/repos/repo_add.html:9
 #: rhodecode/templates/admin/repos/repo_edit.html:9
-#: rhodecode/templates/admin/repos/repos.html:10
+#: rhodecode/templates/admin/repos/repos.html:9
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
@@ -297,546 +366,868 @@ msgstr ""
 #: rhodecode/templates/admin/settings/settings.html:9
 #: rhodecode/templates/admin/users/user_add.html:8
 #: rhodecode/templates/admin/users/user_edit.html:9
-#: rhodecode/templates/admin/users/user_edit.html:110
+#: rhodecode/templates/admin/users/user_edit.html:122
 #: rhodecode/templates/admin/users/users.html:9
 #: rhodecode/templates/admin/users_groups/users_group_add.html:8
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:9
 #: rhodecode/templates/admin/users_groups/users_groups.html:9
-#: rhodecode/templates/base/base.html:279 rhodecode/templates/base/base.html:366
-#: rhodecode/templates/base/base.html:368 rhodecode/templates/base/base.html:370
+#: rhodecode/templates/base/base.html:197 rhodecode/templates/base/base.html:337
+#: rhodecode/templates/base/base.html:339 rhodecode/templates/base/base.html:341
 msgid "Admin"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:62
+#: rhodecode/controllers/admin/permissions.py:65
 msgid "disabled"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:64
+#: rhodecode/controllers/admin/permissions.py:67
 msgid "allowed with manual account activation"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:66
-msgid "allowed with automatic account activation"
-msgstr ""
-
-#: rhodecode/controllers/admin/permissions.py:68
-msgid "Disabled"
-msgstr ""
-
 #: rhodecode/controllers/admin/permissions.py:69
+msgid "allowed with automatic account activation"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:71
+#: rhodecode/controllers/admin/permissions.py:74
+msgid "Disabled"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:72
+#: rhodecode/controllers/admin/permissions.py:75
 msgid "Enabled"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:102
+#: rhodecode/controllers/admin/permissions.py:116
 msgid "Default permissions updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:119
+#: rhodecode/controllers/admin/permissions.py:130
 msgid "error occurred during update of permissions"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:96
-#, 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 ""
-
-#: rhodecode/controllers/admin/repos.py:172
+#: rhodecode/controllers/admin/repos.py:123
+msgid "--REMOVE FORK--"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:192
 #, python-format
 msgid "created repository %s from %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:176
+#: rhodecode/controllers/admin/repos.py:196
 #, python-format
 msgid "created repository %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:205
+#: rhodecode/controllers/admin/repos.py:227
 #, python-format
 msgid "error occurred during creation of repository %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:292
+#: rhodecode/controllers/admin/repos.py:319
 #, python-format
 msgid "Cannot delete %s it still contains attached forks"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:320
+#: rhodecode/controllers/admin/repos.py:348
 msgid "An error occurred during deletion of repository user"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:335
-msgid "An error occurred during deletion of repository users groups"
-msgstr ""
-
-#: rhodecode/controllers/admin/repos.py:352
-msgid "An error occurred during deletion of repository stats"
-msgstr ""
-
 #: rhodecode/controllers/admin/repos.py:367
+msgid "An error occurred during deletion of repository users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:385
+msgid "An error occurred during deletion of repository stats"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:402
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:387
+#: rhodecode/controllers/admin/repos.py:422
+msgid "An error occurred during unlocking"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:442
 msgid "Updated repository visibility in public journal"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:390
+#: rhodecode/controllers/admin/repos.py:446
 msgid "An error occurred during setting this repository in public journal"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:395 rhodecode/model/forms.py:53
+#: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
 msgid "Token mismatch"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:408
+#: rhodecode/controllers/admin/repos.py:464
 msgid "Pulled from remote location"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:410
+#: rhodecode/controllers/admin/repos.py:466
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:83
+#: rhodecode/controllers/admin/repos.py:482
+msgid "Nothing"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:484
+#, python-format
+msgid "Marked repo %s as fork of %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:488
+msgid "An error occurred during this operation"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:116
 #, python-format
 msgid "created repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:96
+#: rhodecode/controllers/admin/repos_groups.py:129
 #, python-format
 msgid "error occurred during creation of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:130
+#: rhodecode/controllers/admin/repos_groups.py:163
 #, python-format
 msgid "updated repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:143
+#: rhodecode/controllers/admin/repos_groups.py:176
 #, python-format
 msgid "error occurred during update of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:164
+#: rhodecode/controllers/admin/repos_groups.py:194
 #, python-format
 msgid "This group contains %s repositores and cannot be deleted"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:171
+#: rhodecode/controllers/admin/repos_groups.py:202
 #, python-format
 msgid "removed repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:175
+#: rhodecode/controllers/admin/repos_groups.py:208
+msgid "Cannot delete this group it still contains subgroups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:213
+#: rhodecode/controllers/admin/repos_groups.py:218
 #, python-format
 msgid "error occurred during deletion of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:109
+#: rhodecode/controllers/admin/repos_groups.py:238
+msgid "An error occurred during deletion of group user"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:258
+msgid "An error occurred during deletion of group users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:121
 #, python-format
 msgid "Repositories successfully rescanned added: %s,removed: %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:118
+#: rhodecode/controllers/admin/settings.py:129
 msgid "Whoosh reindex task scheduled"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:143
+#: rhodecode/controllers/admin/settings.py:160
 msgid "Updated application settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:148
-#: rhodecode/controllers/admin/settings.py:215
+#: rhodecode/controllers/admin/settings.py:164
+#: rhodecode/controllers/admin/settings.py:275
 msgid "error occurred during updating application settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:210
-msgid "Updated mercurial settings"
-msgstr ""
-
-#: rhodecode/controllers/admin/settings.py:236
+#: rhodecode/controllers/admin/settings.py:200
+msgid "Updated visualisation settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:205
+msgid "error occurred during updating visualisation settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:271
+msgid "Updated VCS settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:285
 msgid "Added new hook"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:247
+#: rhodecode/controllers/admin/settings.py:297
 msgid "Updated hooks"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:251
+#: rhodecode/controllers/admin/settings.py:301
 msgid "error occurred during hook creation"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:310
+#: rhodecode/controllers/admin/settings.py:320
+msgid "Email task created"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:375
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:339
+#: rhodecode/controllers/admin/settings.py:406
 msgid "Your account was updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:359
+#: rhodecode/controllers/admin/settings.py:421
+#: rhodecode/controllers/admin/users.py:191
+#, python-format
+msgid "error occurred during update of user %s"
+msgstr ""
+
 #: rhodecode/controllers/admin/users.py:130
 #, python-format
-msgid "error occurred during update of user %s"
-msgstr ""
-
-#: rhodecode/controllers/admin/users.py:78
-#, python-format
 msgid "created user %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:90
+#: rhodecode/controllers/admin/users.py:142
 #, python-format
 msgid "error occurred during creation of user %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:116
+#: rhodecode/controllers/admin/users.py:171
 msgid "User updated successfully"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:146
+#: rhodecode/controllers/admin/users.py:207
 msgid "successfully deleted user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:150
+#: rhodecode/controllers/admin/users.py:212
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:166
+#: rhodecode/controllers/admin/users.py:226
 msgid "You can't edit this user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:195
-#: rhodecode/controllers/admin/users_groups.py:202
+#: rhodecode/controllers/admin/users.py:266
 msgid "Granted 'repository create' permission to user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:204
-#: rhodecode/controllers/admin/users_groups.py:211
+#: rhodecode/controllers/admin/users.py:271
 msgid "Revoked 'repository create' permission to user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:74
+#: rhodecode/controllers/admin/users.py:277
+msgid "Granted 'repository fork' permission to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:282
+msgid "Revoked 'repository fork' permission to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:288
+#: rhodecode/controllers/admin/users_groups.py:255
+msgid "An error occurred during permissions saving"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:303
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:309
+msgid "An error occurred during email saving"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:319
+msgid "Removed email from user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:84
 #, python-format
 msgid "created users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:86
+#: rhodecode/controllers/admin/users_groups.py:95
 #, python-format
 msgid "error occurred during creation of users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:119
+#: rhodecode/controllers/admin/users_groups.py:135
 #, python-format
 msgid "updated users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:138
+#: rhodecode/controllers/admin/users_groups.py:157
 #, python-format
 msgid "error occurred during update of users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:154
+#: rhodecode/controllers/admin/users_groups.py:174
 msgid "successfully deleted users group"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:158
+#: rhodecode/controllers/admin/users_groups.py:179
 msgid "An error occurred during deletion of users group"
 msgstr ""
 
-#: rhodecode/lib/__init__.py:279
-msgid "year"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:280
-msgid "month"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:281
-msgid "day"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:282
-msgid "hour"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:283
-msgid "minute"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:284
-msgid "second"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:293
-msgid "ago"
-msgstr ""
-
-#: rhodecode/lib/__init__.py:296
-msgid "just now"
-msgstr ""
-
-#: rhodecode/lib/auth.py:377
+#: rhodecode/controllers/admin/users_groups.py:233
+msgid "Granted 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:238
+msgid "Revoked 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:244
+msgid "Granted 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:249
+msgid "Revoked 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/lib/auth.py:499
 msgid "You need to be a registered user to perform this action"
 msgstr ""
 
-#: rhodecode/lib/auth.py:421
+#: rhodecode/lib/auth.py:540
 msgid "You need to be a signed in to view this page"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:307
+#: rhodecode/lib/diffs.py:86
+msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: rhodecode/lib/diffs.py:96
+msgid "No changes detected"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:372
+#, python-format
+msgid "%a, %d %b %Y %H:%M:%S"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:484
 msgid "True"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:311
+#: rhodecode/lib/helpers.py:488
 msgid "False"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:352
+#: rhodecode/lib/helpers.py:532
+msgid "Changeset not found"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:555
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:356
+#: rhodecode/lib/helpers.py:561
 msgid "compare view"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:365
-msgid "and"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:365
-#, python-format
-msgid "%s more"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:367 rhodecode/templates/changelog/changelog.html:14
-#: rhodecode/templates/changelog/changelog.html:39
-msgid "revisions"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:385
-msgid "fork name "
-msgstr ""
-
-#: rhodecode/lib/helpers.py:388
-msgid "[deleted] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:389 rhodecode/lib/helpers.py:393
-msgid "[created] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:390 rhodecode/lib/helpers.py:394
-msgid "[forked] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:391 rhodecode/lib/helpers.py:395
-msgid "[updated] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:392
-msgid "[delete] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:396
-msgid "[pushed] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:397
-msgid "[committed via RhodeCode] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:398
-msgid "[pulled from remote] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:399
-msgid "[pulled] from"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:400
-msgid "[started following] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:401
-msgid "[stopped following] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:577
-#, python-format
-msgid " and %s more"
-msgstr ""
-
 #: rhodecode/lib/helpers.py:581
+msgid "and"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:582
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
+msgid "revisions"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:606
+msgid "fork name "
+msgstr ""
+
+#: rhodecode/lib/helpers.py:620
+#: rhodecode/templates/pullrequests/pullrequest_show.html:4
+#: rhodecode/templates/pullrequests/pullrequest_show.html:12
+#, python-format
+msgid "Pull request #%s"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:626
+msgid "[deleted] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
+msgid "[created] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:630
+msgid "[created] repository as fork"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
+msgid "[forked] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
+msgid "[updated] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:636
+msgid "[delete] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:644
+msgid "[created] user"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:646
+msgid "[updated] user"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:648
+msgid "[created] users group"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:650
+msgid "[updated] users group"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:652
+msgid "[commented] on revision in repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:654
+msgid "[commented] on pull request for"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:656
+msgid "[closed] pull request for"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:658
+msgid "[pushed] into"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:660
+msgid "[committed via RhodeCode] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:662
+msgid "[pulled from remote] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:664
+msgid "[pulled] from"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:666
+msgid "[started following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:668
+msgid "[stopped following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:840
+#, python-format
+msgid " and %s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:844
 msgid "No Files"
 msgstr ""
 
-#: rhodecode/model/forms.py:66
-msgid "Invalid username"
-msgstr ""
-
-#: rhodecode/model/forms.py:75
-msgid "This username already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:79
+#: rhodecode/lib/utils2.py:335
+#, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:336
+#, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:337
+#, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:338
+#, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:339
+#, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:340
+#, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/lib/utils2.py:355
+#, python-format
+msgid "%s ago"
+msgstr ""
+
+#: rhodecode/lib/utils2.py:357
+#, python-format
+msgid "%s and %s ago"
+msgstr ""
+
+#: rhodecode/lib/utils2.py:360
+msgid "just now"
+msgstr ""
+
+#: rhodecode/lib/celerylib/tasks.py:269
+msgid "password reset link"
+msgstr ""
+
+#: rhodecode/model/comment.py:110
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: rhodecode/model/comment.py:157
+msgid "[Mention]"
+msgstr ""
+
+#: rhodecode/model/db.py:1140
+msgid "Repository no access"
+msgstr ""
+
+#: rhodecode/model/db.py:1141
+msgid "Repository read access"
+msgstr ""
+
+#: rhodecode/model/db.py:1142
+msgid "Repository write access"
+msgstr ""
+
+#: rhodecode/model/db.py:1143
+msgid "Repository admin access"
+msgstr ""
+
+#: rhodecode/model/db.py:1145
+msgid "Repositories Group no access"
+msgstr ""
+
+#: rhodecode/model/db.py:1146
+msgid "Repositories Group read access"
+msgstr ""
+
+#: rhodecode/model/db.py:1147
+msgid "Repositories Group write access"
+msgstr ""
+
+#: rhodecode/model/db.py:1148
+msgid "Repositories Group admin access"
+msgstr ""
+
+#: rhodecode/model/db.py:1150
+msgid "RhodeCode Administrator"
+msgstr ""
+
+#: rhodecode/model/db.py:1151
+msgid "Repository creation disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1152
+msgid "Repository creation enabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1153
+msgid "Repository forking disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1154
+msgid "Repository forking enabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1155
+msgid "Register disabled"
+msgstr ""
+
+#: rhodecode/model/db.py:1156
+msgid "Register new user with RhodeCode with manual activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1159
+msgid "Register new user with RhodeCode with auto activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1579
+msgid "Not Reviewed"
+msgstr ""
+
+#: rhodecode/model/db.py:1580
+msgid "Approved"
+msgstr ""
+
+#: rhodecode/model/db.py:1581
+msgid "Rejected"
+msgstr ""
+
+#: rhodecode/model/db.py:1582
+msgid "Under Review"
+msgstr ""
+
+#: rhodecode/model/forms.py:43
+msgid "Please enter a login"
+msgstr ""
+
+#: rhodecode/model/forms.py:44
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr ""
+
+#: rhodecode/model/forms.py:52
+msgid "Please enter a password"
+msgstr ""
+
+#: rhodecode/model/forms.py:53
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr ""
+
+#: rhodecode/model/notification.py:220
+msgid "commented on commit"
+msgstr ""
+
+#: rhodecode/model/notification.py:221
+msgid "sent message"
+msgstr ""
+
+#: rhodecode/model/notification.py:222
+msgid "mentioned you"
+msgstr ""
+
+#: rhodecode/model/notification.py:223
+msgid "registered in RhodeCode"
+msgstr ""
+
+#: rhodecode/model/notification.py:224
+msgid "opened new pull request"
+msgstr ""
+
+#: rhodecode/model/notification.py:225
+msgid "commented on pull request"
+msgstr ""
+
+#: rhodecode/model/pull_request.py:84
+#, python-format
+msgid "%(user)s wants you to review pull request #%(pr_id)s"
+msgstr ""
+
+#: rhodecode/model/scm.py:535
+msgid "latest tip"
+msgstr ""
+
+#: rhodecode/model/user.py:230
+msgid "new user registration"
+msgstr ""
+
+#: rhodecode/model/user.py:255 rhodecode/model/user.py:277
+#: rhodecode/model/user.py:299
+msgid "You can't Edit this user since it's crucial for entire application"
+msgstr ""
+
+#: rhodecode/model/user.py:323
+msgid "You can't remove this user since it's crucial for entire application"
+msgstr ""
+
+#: rhodecode/model/user.py:329
+#, python-format
+msgid ""
+"user \"%s\" still owns %s repositories and cannot be removed. Switch owners "
+"or remove those repositories. %s"
+msgstr ""
+
+#: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: rhodecode/model/validators.py:82
+#, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:84
+#, python-format
+msgid "Username \"%(username)s\" is forbidden"
+msgstr ""
+
+#: rhodecode/model/validators.py:86
 msgid ""
 "Username may only contain alphanumeric characters underscores, periods or "
 "dashes and must begin with alphanumeric character"
 msgstr ""
 
-#: rhodecode/model/forms.py:94
-msgid "Invalid group name"
-msgstr ""
-
-#: rhodecode/model/forms.py:104
-msgid "This users group already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:110
+#: rhodecode/model/validators.py:114
+#, python-format
+msgid "Username %(username)s is not valid"
+msgstr ""
+
+#: rhodecode/model/validators.py:133
+msgid "Invalid users group name"
+msgstr ""
+
+#: rhodecode/model/validators.py:134
+#, python-format
+msgid "Users group \"%(usersgroup)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:136
 msgid ""
-"Group name may only contain alphanumeric characters underscores, periods or "
-"dashes and must begin with alphanumeric character"
-msgstr ""
-
-#: rhodecode/model/forms.py:132
+"users group name may only contain  alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr ""
+
+#: rhodecode/model/validators.py:174
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: rhodecode/model/forms.py:148
-msgid "This group already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:164 rhodecode/model/forms.py:172
-#: rhodecode/model/forms.py:180
-msgid "Invalid characters in password"
-msgstr ""
-
-#: rhodecode/model/forms.py:191
+#: rhodecode/model/validators.py:175
+#, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:177
+#, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:235
+msgid "Invalid characters (non-ascii) in password"
+msgstr ""
+
+#: rhodecode/model/validators.py:250
 msgid "Passwords do not match"
 msgstr ""
 
-#: rhodecode/model/forms.py:196
+#: rhodecode/model/validators.py:267
 msgid "invalid password"
 msgstr ""
 
-#: rhodecode/model/forms.py:197
+#: rhodecode/model/validators.py:268
 msgid "invalid user name"
 msgstr ""
 
-#: rhodecode/model/forms.py:198
+#: rhodecode/model/validators.py:269
 msgid "Your account is disabled"
 msgstr ""
 
-#: rhodecode/model/forms.py:233
-msgid "This username is not valid"
-msgstr ""
-
-#: rhodecode/model/forms.py:245
-msgid "This repository name is disallowed"
-msgstr ""
-
-#: rhodecode/model/forms.py:266
+#: rhodecode/model/validators.py:313
+#, python-format
+msgid "Repository name %(repo)s is disallowed"
+msgstr ""
+
+#: rhodecode/model/validators.py:315
 #, python-format
-msgid "This repository already exists in group \"%s\""
-msgstr ""
-
-#: rhodecode/model/forms.py:274
-msgid "This repository already exists"
-msgstr ""
-
-#: rhodecode/model/forms.py:312 rhodecode/model/forms.py:319
+msgid "Repository named %(repo)s already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:316
+#, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr ""
+
+#: rhodecode/model/validators.py:318
+#, python-format
+msgid "Repositories group with name \"%(repo)s\" already exists"
+msgstr ""
+
+#: rhodecode/model/validators.py:431
 msgid "invalid clone url"
 msgstr ""
 
-#: rhodecode/model/forms.py:322
-msgid "Invalid clone url, provide a valid clone http\\s url"
-msgstr ""
-
-#: rhodecode/model/forms.py:334
-msgid "Fork have to be the same type as original"
-msgstr ""
-
-#: rhodecode/model/forms.py:341
+#: rhodecode/model/validators.py:432
+msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
+msgstr ""
+
+#: rhodecode/model/validators.py:457
+msgid "Fork have to be the same type as parent"
+msgstr ""
+
+#: rhodecode/model/validators.py:478
 msgid "This username or users group name is not valid"
 msgstr ""
 
-#: rhodecode/model/forms.py:403
+#: rhodecode/model/validators.py:562
 msgid "This is not a valid path"
 msgstr ""
 
-#: rhodecode/model/forms.py:416
+#: rhodecode/model/validators.py:577
 msgid "This e-mail address is already taken"
 msgstr ""
 
-#: rhodecode/model/forms.py:427
-msgid "This e-mail address doesn't exist."
-msgstr ""
-
-#: rhodecode/model/forms.py:447
+#: rhodecode/model/validators.py:597
+#, python-format
+msgid "e-mail \"%(email)s\" does not exist."
+msgstr ""
+
+#: rhodecode/model/validators.py:634
 msgid ""
 "The LDAP Login attribute of the CN must be specified - this is the name of "
-"the attribute that is equivalent to 'username'"
-msgstr ""
-
-#: rhodecode/model/forms.py:466
-msgid "Please enter a login"
-msgstr ""
-
-#: rhodecode/model/forms.py:467
-#, python-format
-msgid "Enter a value %(min)i characters long or more"
-msgstr ""
-
-#: rhodecode/model/forms.py:475
-msgid "Please enter a password"
-msgstr ""
-
-#: rhodecode/model/forms.py:476
+"the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: rhodecode/model/validators.py:653
 #, python-format
-msgid "Enter %(min)i characters or more"
-msgstr ""
-
-#: rhodecode/model/user.py:145
-msgid "[RhodeCode] New User registration"
-msgstr ""
-
-#: rhodecode/model/user.py:157 rhodecode/model/user.py:179
-msgid "You can't Edit this user since it's crucial for entire application"
-msgstr ""
-
-#: rhodecode/model/user.py:201
-msgid "You can't remove this user since it's crucial for entire application"
-msgstr ""
-
-#: rhodecode/model/user.py:204
-#, python-format
-msgid ""
-"This user still owns %s repositories and cannot be removed. Switch owners or "
-"remove those repositories"
-msgstr ""
-
-#: rhodecode/templates/index.html:4
+msgid "Revisions %(revs)s are already part of pull request or have set status"
+msgstr ""
+
+#: rhodecode/templates/index.html:3
 msgid "Dashboard"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:22
-#: rhodecode/templates/admin/users/user_edit_my_account.html:102
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/repo_switcher_list.html:4
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/users/user_edit_my_account.html:31
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/bookmarks/bookmarks.html:10
+#: rhodecode/templates/branches/branches.html:9
+#: rhodecode/templates/journal/journal.html:40
+#: rhodecode/templates/tags/tags.html:10
 msgid "quick filter..."
 msgstr ""
 
-#: rhodecode/templates/index_base.html:23 rhodecode/templates/base/base.html:300
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/base/base.html:221
 msgid "repositories"
 msgstr ""
 
+#: rhodecode/templates/index_base.html:13 rhodecode/templates/index_base.html:15
+#: rhodecode/templates/admin/repos/repos.html:21
+msgid "ADD REPOSITORY"
+msgstr ""
+
 #: rhodecode/templates/index_base.html:29
-#: rhodecode/templates/admin/repos/repos.html:22
-msgid "ADD NEW REPOSITORY"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
@@ -845,145 +1236,148 @@ msgstr ""
 msgid "Group name"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:42 rhodecode/templates/index_base.html:73
-#: rhodecode/templates/admin/repos/repo_add_base.html:44
-#: rhodecode/templates/admin/repos/repo_edit.html:64
-#: rhodecode/templates/admin/repos/repos.html:31
+#: rhodecode/templates/index_base.html:30 rhodecode/templates/index_base.html:71
+#: rhodecode/templates/index_base.html:142 rhodecode/templates/index_base.html:168
+#: rhodecode/templates/admin/repos/repo_add_base.html:56
+#: rhodecode/templates/admin/repos/repo_edit.html:75
+#: rhodecode/templates/admin/repos/repos.html:72
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
-#: rhodecode/templates/settings/repo_fork.html:40
-#: rhodecode/templates/settings/repo_settings.html:40
-#: rhodecode/templates/summary/summary.html:92
+#: rhodecode/templates/forks/fork.html:59
+#: rhodecode/templates/settings/repo_settings.html:66
+#: rhodecode/templates/summary/summary.html:105
 msgid "Description"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:53
+#: rhodecode/templates/index_base.html:40
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
 msgid "Repositories group"
 msgstr ""
 
+#: rhodecode/templates/index_base.html:70 rhodecode/templates/index_base.html:166
+#: rhodecode/templates/admin/repos/repo_add_base.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:32
+#: rhodecode/templates/admin/repos/repos.html:70
+#: rhodecode/templates/admin/users/user_edit.html:192
+#: rhodecode/templates/admin/users/user_edit_my_account.html:59
+#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
+#: rhodecode/templates/bookmarks/bookmarks.html:36
+#: rhodecode/templates/bookmarks/bookmarks_data.html:6
+#: rhodecode/templates/branches/branches.html:51
+#: rhodecode/templates/files/files_browser.html:47
+#: rhodecode/templates/journal/journal.html:59
+#: rhodecode/templates/journal/journal.html:107
+#: rhodecode/templates/journal/journal.html:186
+#: rhodecode/templates/settings/repo_settings.html:31
+#: rhodecode/templates/summary/summary.html:43
+#: rhodecode/templates/summary/summary.html:123
+#: rhodecode/templates/tags/tags.html:36 rhodecode/templates/tags/tags_data.html:6
+msgid "Name"
+msgstr ""
+
 #: rhodecode/templates/index_base.html:72
-#: rhodecode/templates/admin/repos/repo_add_base.html:9
-#: rhodecode/templates/admin/repos/repo_edit.html:32
-#: rhodecode/templates/admin/repos/repos.html:30
-#: rhodecode/templates/admin/users/user_edit_my_account.html:117
-#: rhodecode/templates/files/files_browser.html:157
-#: rhodecode/templates/settings/repo_settings.html:31
-#: rhodecode/templates/summary/summary.html:31
-#: rhodecode/templates/summary/summary.html:107
-msgid "Name"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:74
-#: rhodecode/templates/admin/repos/repos.html:32
-#: rhodecode/templates/summary/summary.html:114
 msgid "Last change"
 msgstr ""
 
+#: rhodecode/templates/index_base.html:73 rhodecode/templates/index_base.html:171
+#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/journal/journal.html:188
+msgid "Tip"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:74 rhodecode/templates/index_base.html:173
+#: rhodecode/templates/admin/repos/repo_edit.html:121
+#: rhodecode/templates/admin/repos/repos.html:73
+msgid "Owner"
+msgstr ""
+
 #: rhodecode/templates/index_base.html:75
-#: rhodecode/templates/admin/repos/repos.html:33
-msgid "Tip"
+#: rhodecode/templates/summary/summary.html:48
+#: rhodecode/templates/summary/summary.html:51
+msgid "RSS"
 msgstr ""
 
 #: rhodecode/templates/index_base.html:76
-#: rhodecode/templates/admin/repos/repo_edit.html:97
-msgid "Owner"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:77
-#: rhodecode/templates/journal/public_journal.html:20
-#: rhodecode/templates/summary/summary.html:180
-#: rhodecode/templates/summary/summary.html:183
-msgid "RSS"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:78
-#: rhodecode/templates/journal/public_journal.html:23
-#: rhodecode/templates/summary/summary.html:181
-#: rhodecode/templates/summary/summary.html:184
 msgid "Atom"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:87 rhodecode/templates/index_base.html:89
-#: rhodecode/templates/index_base.html:91 rhodecode/templates/base/base.html:209
-#: rhodecode/templates/base/base.html:211 rhodecode/templates/base/base.html:213
-#: rhodecode/templates/summary/summary.html:4
-msgid "Summary"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:95 rhodecode/templates/index_base.html:97
-#: rhodecode/templates/index_base.html:99 rhodecode/templates/base/base.html:225
-#: rhodecode/templates/base/base.html:227 rhodecode/templates/base/base.html:229
-#: rhodecode/templates/changelog/changelog.html:6
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "Changelog"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:103 rhodecode/templates/index_base.html:105
-#: rhodecode/templates/index_base.html:107 rhodecode/templates/base/base.html:268
-#: rhodecode/templates/base/base.html:270 rhodecode/templates/base/base.html:272
-#: rhodecode/templates/files/files.html:4
-msgid "Files"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:116
-#: rhodecode/templates/admin/repos/repos.html:42
-#: rhodecode/templates/admin/users/user_edit_my_account.html:127
-#: rhodecode/templates/summary/summary.html:48
-msgid "Mercurial repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:118
-#: rhodecode/templates/admin/repos/repos.html:44
-#: rhodecode/templates/admin/users/user_edit_my_account.html:129
-#: rhodecode/templates/summary/summary.html:51
-msgid "Git repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:123
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
-#: rhodecode/templates/journal/journal.html:53
-#: rhodecode/templates/summary/summary.html:56
-msgid "private repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:125
-#: rhodecode/templates/journal/journal.html:55
-#: rhodecode/templates/summary/summary.html:58
-msgid "public repository"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:133 rhodecode/templates/base/base.html:291
-#: rhodecode/templates/settings/repo_fork.html:13
-msgid "fork"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:134
-#: rhodecode/templates/admin/repos/repos.html:60
-#: rhodecode/templates/admin/users/user_edit_my_account.html:143
-#: rhodecode/templates/summary/summary.html:69
-#: rhodecode/templates/summary/summary.html:71
-msgid "Fork of"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:155
-#: rhodecode/templates/admin/repos/repos.html:73
-msgid "No changesets yet"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:161 rhodecode/templates/index_base.html:163
+#: rhodecode/templates/index_base.html:110 rhodecode/templates/index_base.html:112
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:168 rhodecode/templates/index_base.html:170
+#: rhodecode/templates/index_base.html:117 rhodecode/templates/index_base.html:119
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr ""
 
+#: rhodecode/templates/index_base.html:140
+msgid "Group Name"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:158 rhodecode/templates/index_base.html:198
+#: rhodecode/templates/admin/repos/repos.html:94
+#: rhodecode/templates/admin/users/user_edit_my_account.html:179
+#: rhodecode/templates/admin/users/users.html:107
+#: rhodecode/templates/bookmarks/bookmarks.html:60
+#: rhodecode/templates/branches/branches.html:77
+#: rhodecode/templates/journal/journal.html:211
+#: rhodecode/templates/tags/tags.html:60
+msgid "Click to sort ascending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:159 rhodecode/templates/index_base.html:199
+#: rhodecode/templates/admin/repos/repos.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account.html:180
+#: rhodecode/templates/admin/users/users.html:108
+#: rhodecode/templates/bookmarks/bookmarks.html:61
+#: rhodecode/templates/branches/branches.html:78
+#: rhodecode/templates/journal/journal.html:212
+#: rhodecode/templates/tags/tags.html:61
+msgid "Click to sort descending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:169
+msgid "Last Change"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:200
+#: rhodecode/templates/admin/repos/repos.html:96
+#: rhodecode/templates/admin/users/user_edit_my_account.html:181
+#: rhodecode/templates/admin/users/users.html:109
+#: rhodecode/templates/bookmarks/bookmarks.html:62
+#: rhodecode/templates/branches/branches.html:79
+#: rhodecode/templates/journal/journal.html:213
+#: rhodecode/templates/tags/tags.html:62
+msgid "No records found."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:201
+#: rhodecode/templates/admin/repos/repos.html:97
+#: rhodecode/templates/admin/users/user_edit_my_account.html:182
+#: rhodecode/templates/admin/users/users.html:110
+#: rhodecode/templates/bookmarks/bookmarks.html:63
+#: rhodecode/templates/branches/branches.html:80
+#: rhodecode/templates/journal/journal.html:214
+#: rhodecode/templates/tags/tags.html:63
+msgid "Data error."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:202
+#: rhodecode/templates/admin/repos/repos.html:98
+#: rhodecode/templates/admin/users/user_edit_my_account.html:183
+#: rhodecode/templates/admin/users/users.html:111
+#: rhodecode/templates/bookmarks/bookmarks.html:64
+#: rhodecode/templates/branches/branches.html:81
+#: rhodecode/templates/journal/journal.html:215
+#: rhodecode/templates/tags/tags.html:64
+msgid "Loading..."
+msgstr ""
+
 #: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
-#: rhodecode/templates/base/base.html:38
 msgid "Sign In"
 msgstr ""
 
@@ -994,25 +1388,29 @@ msgstr ""
 #: 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:47
-#: rhodecode/templates/admin/users/user_edit_my_account.html:45
-#: rhodecode/templates/base/base.html:15
-#: rhodecode/templates/summary/summary.html:106
+#: rhodecode/templates/admin/users/user_edit.html:50
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
+#: rhodecode/templates/base/base.html:83
+#: rhodecode/templates/summary/summary.html:122
 msgid "Username"
 msgstr ""
 
 #: 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:24
+#: rhodecode/templates/base/base.html:92
 msgid "Password"
 msgstr ""
 
+#: rhodecode/templates/login.html:50
+msgid "Remember me"
+msgstr ""
+
 #: rhodecode/templates/login.html:60
 msgid "Forgot your password ?"
 msgstr ""
 
-#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:35
+#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
 msgid "Don't have an account ?"
 msgstr ""
 
@@ -1049,24 +1447,24 @@ msgid "Re-enter password"
 msgstr ""
 
 #: rhodecode/templates/register.html:47
-#: rhodecode/templates/admin/users/user_add.html:50
-#: rhodecode/templates/admin/users/user_edit.html:74
-#: rhodecode/templates/admin/users/user_edit_my_account.html:63
+#: rhodecode/templates/admin/users/user_add.html:59
+#: rhodecode/templates/admin/users/user_edit.html:86
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
 msgid "First Name"
 msgstr ""
 
 #: rhodecode/templates/register.html:56
-#: rhodecode/templates/admin/users/user_add.html:59
-#: rhodecode/templates/admin/users/user_edit.html:83
-#: rhodecode/templates/admin/users/user_edit_my_account.html:72
+#: rhodecode/templates/admin/users/user_add.html:68
+#: rhodecode/templates/admin/users/user_edit.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
 msgid "Last Name"
 msgstr ""
 
 #: rhodecode/templates/register.html:65
-#: rhodecode/templates/admin/users/user_add.html:68
-#: rhodecode/templates/admin/users/user_edit.html:92
-#: rhodecode/templates/admin/users/user_edit_my_account.html:81
-#: rhodecode/templates/summary/summary.html:108
+#: rhodecode/templates/admin/users/user_add.html:77
+#: rhodecode/templates/admin/users/user_edit.html:104
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
+#: rhodecode/templates/summary/summary.html:124
 msgid "Email"
 msgstr ""
 
@@ -1078,19 +1476,58 @@ msgstr ""
 msgid "Your account must wait for activation by administrator"
 msgstr ""
 
-#: rhodecode/templates/repo_switcher_list.html:14
+#: rhodecode/templates/repo_switcher_list.html:11
+#: rhodecode/templates/admin/repos/repo_add_base.html:65
+#: rhodecode/templates/admin/repos/repo_edit.html:85
+#: rhodecode/templates/settings/repo_settings.html:76
 msgid "Private repository"
 msgstr ""
 
-#: rhodecode/templates/repo_switcher_list.html:19
+#: rhodecode/templates/repo_switcher_list.html:16
 msgid "Public repository"
 msgstr ""
 
+#: rhodecode/templates/switch_to_list.html:3
+#: rhodecode/templates/branches/branches.html:14
+msgid "branches"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:10
+#: rhodecode/templates/branches/branches_data.html:57
+msgid "There are no branches yet"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:15
+#: rhodecode/templates/shortlog/shortlog_data.html:10
+#: rhodecode/templates/tags/tags.html:15
+msgid "tags"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:22
+#: rhodecode/templates/tags/tags_data.html:33
+msgid "There are no tags yet"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:28
+#: rhodecode/templates/bookmarks/bookmarks.html:15
+msgid "bookmarks"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:35
+#: rhodecode/templates/bookmarks/bookmarks_data.html:32
+msgid "There are no bookmarks yet"
+msgstr ""
+
 #: rhodecode/templates/admin/admin.html:5 rhodecode/templates/admin/admin.html:9
 msgid "Admin journal"
 msgstr ""
 
 #: rhodecode/templates/admin/admin_log.html:6
+#: rhodecode/templates/admin/repos/repos.html:74
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
+#: rhodecode/templates/journal/journal.html:61
+#: rhodecode/templates/journal/journal.html:62
 msgid "Action"
 msgstr ""
 
@@ -1099,6 +1536,10 @@ msgid "Repository"
 msgstr ""
 
 #: rhodecode/templates/admin/admin_log.html:8
+#: rhodecode/templates/bookmarks/bookmarks.html:37
+#: rhodecode/templates/bookmarks/bookmarks_data.html:7
+#: rhodecode/templates/branches/branches.html:52
+#: rhodecode/templates/tags/tags.html:37 rhodecode/templates/tags/tags_data.html:7
 msgid "Date"
 msgstr ""
 
@@ -1106,7 +1547,7 @@ msgstr ""
 msgid "From IP"
 msgstr ""
 
-#: rhodecode/templates/admin/admin_log.html:52
+#: rhodecode/templates/admin/admin_log.html:53
 msgid "No actions yet"
 msgstr ""
 
@@ -1183,23 +1624,62 @@ msgid "E-mail Attribute"
 msgstr ""
 
 #: rhodecode/templates/admin/ldap/ldap.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:141
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
 #: rhodecode/templates/admin/settings/hooks.html:73
-#: rhodecode/templates/admin/users/user_edit.html:117
-#: rhodecode/templates/admin/users/user_edit.html:142
-#: rhodecode/templates/admin/users/user_edit_my_account.html:89
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:263
+#: rhodecode/templates/admin/users/user_edit.html:129
+#: rhodecode/templates/admin/users/user_edit.html:174
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:135
+#: rhodecode/templates/settings/repo_settings.html:93
 msgid "Save"
 msgstr ""
 
+#: rhodecode/templates/admin/notifications/notifications.html:5
+#: rhodecode/templates/admin/notifications/notifications.html:9
+msgid "My Notifications"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:29
+msgid "All"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:30
+msgid "Comments"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:31
+#: rhodecode/templates/base/base.html:254 rhodecode/templates/base/base.html:256
+msgid "Pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:35
+msgid "Mark all read"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications_data.html:39
+msgid "No notifications here yet"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/show_notification.html:5
+#: rhodecode/templates/admin/notifications/show_notification.html:11
+msgid "Show notification"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/show_notification.html:9
+msgid "Notifications"
+msgstr ""
+
 #: rhodecode/templates/admin/permissions/permissions.html:5
 msgid "Permissions administration"
 msgstr ""
 
 #: rhodecode/templates/admin/permissions/permissions.html:11
-#: rhodecode/templates/admin/repos/repo_edit.html:109
-#: rhodecode/templates/admin/users/user_edit.html:127
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:248
-#: rhodecode/templates/settings/repo_settings.html:58
+#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
+#: rhodecode/templates/admin/users/user_edit.html:139
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:100
+#: rhodecode/templates/settings/repo_settings.html:86
 msgid "Permissions"
 msgstr ""
 
@@ -1235,6 +1715,11 @@ msgid "Repository creation"
 msgstr ""
 
 #: rhodecode/templates/admin/permissions/permissions.html:71
+msgid "Repository forking"
+msgstr ""
+
+#: rhodecode/templates/admin/permissions/permissions.html:78
+#: rhodecode/templates/admin/repos/repo_edit.html:241
 msgid "set"
 msgstr ""
 
@@ -1245,7 +1730,6 @@ msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_add.html:11
 #: rhodecode/templates/admin/repos/repo_edit.html:11
-#: rhodecode/templates/admin/repos/repos.html:10
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
 msgid "Repositories"
 msgstr ""
@@ -1255,30 +1739,70 @@ msgid "add new"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_add_base.html:20
-#: rhodecode/templates/summary/summary.html:80
-#: rhodecode/templates/summary/summary.html:82
+#: rhodecode/templates/summary/summary.html:95
+#: rhodecode/templates/summary/summary.html:96
 msgid "Clone from"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:28
-#: rhodecode/templates/admin/repos/repo_edit.html:48
+#: rhodecode/templates/admin/repos/repo_add_base.html:24
+#: rhodecode/templates/admin/repos/repo_edit.html:44
+#: rhodecode/templates/settings/repo_settings.html:43
+msgid "Optional http[s] url from which repository should be cloned."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:29
+#: rhodecode/templates/admin/repos/repo_edit.html:49
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:4
+#: rhodecode/templates/forks/fork.html:50
+#: rhodecode/templates/settings/repo_settings.html:48
 msgid "Repository group"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:36
-#: rhodecode/templates/admin/repos/repo_edit.html:56
+#: rhodecode/templates/admin/repos/repo_add_base.html:33
+#: rhodecode/templates/forks/fork.html:54
+msgid "Optionaly select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:38
+#: rhodecode/templates/admin/repos/repo_edit.html:58
 msgid "Type"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:52
-#: rhodecode/templates/admin/repos/repo_edit.html:73
-#: rhodecode/templates/settings/repo_fork.html:48
-#: rhodecode/templates/settings/repo_settings.html:49
-msgid "Private"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_add_base.html:59
+#: rhodecode/templates/admin/repos/repo_add_base.html:42
+msgid "Type of repository to create."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:47
+#: rhodecode/templates/admin/repos/repo_edit.html:66
+#: rhodecode/templates/forks/fork.html:41
+#: rhodecode/templates/settings/repo_settings.html:57
+msgid "Landing revision"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:51
+#: rhodecode/templates/admin/repos/repo_edit.html:70
+#: rhodecode/templates/forks/fork.html:45
+#: rhodecode/templates/settings/repo_settings.html:61
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:60
+#: 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."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:69
+#: 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 ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:73
 msgid "add"
 msgstr ""
 
@@ -1292,183 +1816,268 @@ msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit.html:13
 #: rhodecode/templates/admin/users/user_edit.html:13
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
+#: rhodecode/templates/admin/users/user_edit.html:224
+#: rhodecode/templates/admin/users/user_edit.html:226
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:13
-#: rhodecode/templates/files/files_annotate.html:49
-#: rhodecode/templates/files/files_source.html:20
+#: rhodecode/templates/files/files_source.html:44
+#: rhodecode/templates/journal/journal.html:81
 msgid "edit"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit.html:40
+#: rhodecode/templates/settings/repo_settings.html:39
 msgid "Clone uri"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:81
+#: rhodecode/templates/admin/repos/repo_edit.html:53
+#: rhodecode/templates/settings/repo_settings.html:52
+msgid "Optional select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:94
 msgid "Enable statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:98
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:103
 msgid "Enable downloads"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:127
+#: rhodecode/templates/admin/repos/repo_edit.html:107
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:112
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
+msgid "Enable locking"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:116
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:126
+msgid "Change owner of this repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:142
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
+#: rhodecode/templates/admin/settings/settings.html:113
+#: rhodecode/templates/admin/settings/settings.html:168
+#: rhodecode/templates/admin/settings/settings.html:258
+#: rhodecode/templates/admin/users/user_edit.html:130
+#: rhodecode/templates/admin/users/user_edit.html:175
+#: rhodecode/templates/admin/users/user_edit.html:278
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:136
+#: rhodecode/templates/files/files_add.html:82
+#: rhodecode/templates/files/files_edit.html:68
+#: rhodecode/templates/pullrequests/pullrequest.html:124
+#: rhodecode/templates/settings/repo_settings.html:94
+msgid "Reset"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:152
 msgid "Administration"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:130
+#: rhodecode/templates/admin/repos/repo_edit.html:155
 msgid "Statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Reset current statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Confirm to remove current statistics"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:137
-msgid "Fetched to rev"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:138
-msgid "Percentage of stats gathered"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:147
-msgid "Remote"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:151
-msgid "Pull changes from remote location"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:151
-msgid "Confirm to pull changes from remote side"
-msgstr ""
-
 #: rhodecode/templates/admin/repos/repo_edit.html:162
+msgid "Fetched to rev"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:163
+msgid "Stats gathered"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:171
+msgid "Remote"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Pull changes from remote location"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Confirm to pull changes from remote side"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:186
 msgid "Cache"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Invalidate repository cache"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Confirm to invalidate repository cache"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:177
+#: rhodecode/templates/admin/repos/repo_edit.html:195
+#: rhodecode/templates/base/base.html:318 rhodecode/templates/base/base.html:320
+#: rhodecode/templates/base/base.html:322
+msgid "Public journal"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:201
 msgid "Remove from public journal"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:179
+#: rhodecode/templates/admin/repos/repo_edit.html:203
 msgid "Add to public journal"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:185
+#: rhodecode/templates/admin/repos/repo_edit.html:208
+msgid ""
+"All actions made on this repository will be accessible to everyone in public "
+"journal"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:215
+msgid "Locking"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Unlock locked repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Confirm to unlock repository"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "lock repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "Confirm to lock repository"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:224
+msgid "Repository is not locked"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:229
+msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:236
+msgid "Set as fork of"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:245
+msgid "Manually set this repository as a fork of another from the list"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:251
+#: rhodecode/templates/changeset/changeset_file_comment.html:26
 msgid "Delete"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
+#: rhodecode/templates/admin/repos/repo_edit.html:255
 msgid "Remove this repository"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
-#: rhodecode/templates/admin/repos/repos.html:79
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+#: rhodecode/templates/journal/journal.html:84
 msgid "Confirm to delete this repository"
 msgstr ""
 
+#: rhodecode/templates/admin/repos/repo_edit.html:259
+msgid ""
+"This repository will be renamed in a special way in order to be unaccesible "
+"for RhodeCode and VCS systems.\n"
+"                         If you need fully delete it from filesystem please "
+"do it manually"
+msgstr ""
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:3
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
 msgid "none"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:4
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
 msgid "read"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:5
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
 msgid "write"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:6
-#: rhodecode/templates/admin/users/users.html:38
-#: rhodecode/templates/base/base.html:296
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
+#: rhodecode/templates/admin/users/users.html:85
+#: rhodecode/templates/base/base.html:217
 msgid "admin"
 msgstr ""
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:7
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
 msgid "member"
 msgstr ""
 
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
+#: rhodecode/templates/data_table/_dt_elements.html:67
+#: rhodecode/templates/journal/journal.html:132
+#: rhodecode/templates/summary/summary.html:76
+msgid "private repository"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:19
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:28
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
+msgid "default"
+msgstr ""
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:33
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:53
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:58
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
 msgid "revoke"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:75
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:83
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
 msgid "Add another member"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:89
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:97
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
 msgid "Failed to remove user"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:104
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:112
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
 msgid "Failed to remove users group"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:205
-msgid "Group"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:206
-#: rhodecode/templates/admin/users_groups/users_groups.html:33
-msgid "members"
-msgstr ""
-
 #: rhodecode/templates/admin/repos/repos.html:5
 msgid "Repositories administration"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repos.html:34
-#: rhodecode/templates/summary/summary.html:100
-msgid "Contact"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:35
-#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
-#: rhodecode/templates/admin/users/user_edit_my_account.html:119
-#: rhodecode/templates/admin/users/users.html:40
-#: rhodecode/templates/admin/users_groups/users_groups.html:35
-msgid "action"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:51
-#: rhodecode/templates/admin/users/user_edit_my_account.html:134
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
-msgid "private"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:53
-#: rhodecode/templates/admin/repos/repos.html:59
-#: rhodecode/templates/admin/users/user_edit_my_account.html:136
-#: rhodecode/templates/admin/users/user_edit_my_account.html:142
-#: rhodecode/templates/summary/summary.html:68
-msgid "public"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repos.html:79
-#: rhodecode/templates/admin/users/users.html:55
-msgid "delete"
-msgstr ""
-
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:8
 msgid "Groups"
 msgstr ""
 
-#: rhodecode/templates/admin/repos_groups/repos_groups.html:13
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:12
 msgid "with"
 msgstr ""
 
@@ -1491,10 +2100,10 @@ msgid "Group parent"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
-#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
-#: rhodecode/templates/admin/users/user_add.html:85
+#: rhodecode/templates/admin/users/user_add.html:94
 #: rhodecode/templates/admin/users_groups/users_group_add.html:49
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:90
+#: rhodecode/templates/pullrequests/pullrequest_show.html:113
 msgid "save"
 msgstr ""
 
@@ -1506,6 +2115,12 @@ msgstr ""
 msgid "edit repos group"
 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"
+msgstr ""
+
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
 msgid "Repositories groups administration"
 msgstr ""
@@ -1515,11 +2130,26 @@ msgid "ADD NEW GROUP"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
-msgid "Number of repositories"
+msgid "Number of toplevel repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
+#: rhodecode/templates/admin/users/users.html:87
+#: rhodecode/templates/admin/users_groups/users_groups.html:35
+msgid "action"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
-msgid "Confirm to delete this group"
+#: rhodecode/templates/admin/users/user_edit.html:255
+#: rhodecode/templates/admin/users_groups/users_groups.html:44
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#: rhodecode/templates/data_table/_dt_elements.html:103
+msgid "delete"
+msgstr ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#, python-format
+msgid "Confirm to delete this group: %s"
 msgstr ""
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
@@ -1533,7 +2163,6 @@ msgstr ""
 
 #: rhodecode/templates/admin/settings/hooks.html:9
 #: rhodecode/templates/admin/settings/settings.html:9
-#: rhodecode/templates/settings/repo_settings.html:5
 #: rhodecode/templates/settings/repo_settings.html:13
 msgid "Settings"
 msgstr ""
@@ -1573,115 +2202,185 @@ msgstr ""
 msgid "destroy old data"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:45
+#: rhodecode/templates/admin/settings/settings.html:41
+msgid ""
+"Rescan repositories location for new repositories. Also deletes obsolete if "
+"`destroy` flag is checked "
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:46
 msgid "Rescan repositories"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:51
+#: rhodecode/templates/admin/settings/settings.html:52
 msgid "Whoosh indexing"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:59
+#: rhodecode/templates/admin/settings/settings.html:60
 msgid "index build option"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:64
+#: rhodecode/templates/admin/settings/settings.html:65
 msgid "build from scratch"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:70
+#: rhodecode/templates/admin/settings/settings.html:71
 msgid "Reindex"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:76
+#: rhodecode/templates/admin/settings/settings.html:77
 msgid "Global application settings"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:85
+#: rhodecode/templates/admin/settings/settings.html:86
 msgid "Application name"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:94
+#: rhodecode/templates/admin/settings/settings.html:95
 msgid "Realm text"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:103
+#: rhodecode/templates/admin/settings/settings.html:104
 msgid "GA code"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:111
-#: rhodecode/templates/admin/settings/settings.html:177
-msgid "Save settings"
-msgstr ""
-
 #: rhodecode/templates/admin/settings/settings.html:112
-#: rhodecode/templates/admin/settings/settings.html:178
-#: rhodecode/templates/admin/users/user_edit.html:118
-#: rhodecode/templates/admin/users/user_edit.html:143
-#: rhodecode/templates/admin/users/user_edit_my_account.html:90
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:264
-#: rhodecode/templates/files/files_edit.html:50
-msgid "Reset"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:118
-msgid "Mercurial settings"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:127
+#: rhodecode/templates/admin/settings/settings.html:167
+#: rhodecode/templates/admin/settings/settings.html:257
+msgid "Save settings"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:119
+msgid "Visualisation settings"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:128
+msgid "Icons"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:133
+msgid "Show public repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:137
+msgid "Show private repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:144
+msgid "Meta-Tagging"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:149
+msgid "Stylify recognised metatags:"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:176
+msgid "VCS settings"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:185
 msgid "Web"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:132
-msgid "require ssl for pushing"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:139
+#: rhodecode/templates/admin/settings/settings.html:190
+msgid "require ssl for vcs operations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:192
+msgid ""
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it will "
+"return HTTP Error 406: Not Acceptable"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:198
 msgid "Hooks"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:142
-msgid "advanced setup"
-msgstr ""
-
-#: rhodecode/templates/admin/settings/settings.html:147
+#: rhodecode/templates/admin/settings/settings.html:203
 msgid "Update repository after push (hg update)"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:151
+#: rhodecode/templates/admin/settings/settings.html:207
 msgid "Show repository size after push"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:155
+#: rhodecode/templates/admin/settings/settings.html:211
 msgid "Log user push commands"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:159
+#: rhodecode/templates/admin/settings/settings.html:215
 msgid "Log user pull commands"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:166
+#: rhodecode/templates/admin/settings/settings.html:219
+msgid "advanced setup"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:224
+msgid "Mercurial Extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:229
+msgid "largefiles extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:233
+msgid "hgsubversion extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:235
+msgid ""
+"Requires hgsubversion library installed. Allows clonning from svn remote "
+"locations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:245
 msgid "Repositories location"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:171
+#: rhodecode/templates/admin/settings/settings.html:250
 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 ""
 
-#: rhodecode/templates/admin/settings/settings.html:172
+#: rhodecode/templates/admin/settings/settings.html:251
 msgid "unlock"
 msgstr ""
 
+#: rhodecode/templates/admin/settings/settings.html:252
+msgid ""
+"Location where repositories are stored. After changing this value a restart, "
+"and rescan is required"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:272
+msgid "Test Email"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:280
+msgid "Email to"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:288
+msgid "Send"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:294
+msgid "System Info and Packages"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:297
+msgid "show"
+msgstr ""
+
 #: rhodecode/templates/admin/users/user_add.html:5
 msgid "Add user"
 msgstr ""
 
 #: rhodecode/templates/admin/users/user_add.html:10
 #: rhodecode/templates/admin/users/user_edit.html:11
-#: rhodecode/templates/admin/users/users.html:9
 msgid "Users"
 msgstr ""
 
@@ -1689,8 +2388,12 @@ msgstr ""
 msgid "add new user"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_add.html:77
-#: rhodecode/templates/admin/users/user_edit.html:101
+#: rhodecode/templates/admin/users/user_add.html:50
+msgid "Password confirmation"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_add.html:86
+#: rhodecode/templates/admin/users/user_edit.html:113
 #: rhodecode/templates/admin/users_groups/users_group_add.html:41
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:42
 msgid "Active"
@@ -1700,36 +2403,93 @@ msgstr ""
 msgid "Edit user"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:33
-#: rhodecode/templates/admin/users/user_edit_my_account.html:32
+#: rhodecode/templates/admin/users/user_edit.html:34
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
 msgid "Change your avatar at"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:34
-#: rhodecode/templates/admin/users/user_edit_my_account.html:33
+#: rhodecode/templates/admin/users/user_edit.html:35
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
 msgid "Using"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:40
-#: rhodecode/templates/admin/users/user_edit_my_account.html:39
+#: rhodecode/templates/admin/users/user_edit.html:43
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
 msgid "API key"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:56
+#: rhodecode/templates/admin/users/user_edit.html:59
 msgid "LDAP DN"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:65
-#: rhodecode/templates/admin/users/user_edit_my_account.html:54
+#: rhodecode/templates/admin/users/user_edit.html:68
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
 msgid "New password"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:135
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:256
+#: rhodecode/templates/admin/users/user_edit.html:77
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
+msgid "New password confirmation"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:147
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:108
+msgid "Inherit default permissions"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:152
+#: 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 ""
+
+#: rhodecode/templates/admin/users/user_edit.html:158
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
 msgid "Create repositories"
 msgstr ""
 
+#: rhodecode/templates/admin/users/user_edit.html:166
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:127
+msgid "Fork repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:186
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
+msgid "Nothing here yet"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account.html:60
+#: rhodecode/templates/admin/users/user_edit_my_account.html:194
+msgid "Permission"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:194
+msgid "Edit Permission"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:243
+msgid "Email addresses"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:256
+#, python-format
+msgid "Confirm to delete this email: %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:270
+msgid "New email address"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:277
+msgid "Add"
+msgstr ""
+
 #: rhodecode/templates/admin/users/user_edit_my_account.html:5
+#: rhodecode/templates/base/base.html:124
 msgid "My account"
 msgstr ""
 
@@ -1737,26 +2497,73 @@ msgstr ""
 msgid "My Account"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:101
-msgid "My repositories"
-msgstr ""
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:107
-msgid "ADD REPOSITORY"
-msgstr ""
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:118
-#: rhodecode/templates/branches/branches_data.html:7
-#: rhodecode/templates/shortlog/shortlog_data.html:8
-#: rhodecode/templates/tags/tags_data.html:7
-msgid "revision"
-msgstr ""
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:35
+msgid "My permissions"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:38
+#: rhodecode/templates/journal/journal.html:41
+msgid "My repos"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:41
+msgid "My pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:45
+msgid "Add repo"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
+msgid "Opened by me"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
+#, python-format
+msgid "Pull request #%s opened on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
+msgid "Confirm to delete this pull request"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
+msgid "I participate in"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
+#, python-format
+msgid "Pull request #%s opened by %s on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
+#: rhodecode/templates/bookmarks/bookmarks.html:40
+#: rhodecode/templates/bookmarks/bookmarks_data.html:9
+#: rhodecode/templates/branches/branches.html:55
+#: rhodecode/templates/journal/journal.html:60
+#: rhodecode/templates/tags/tags.html:40 rhodecode/templates/tags/tags_data.html:9
+msgid "Revision"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/journal/journal.html:81
+msgid "private"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
+#: rhodecode/templates/journal/journal.html:94
 msgid "No repositories yet"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
+#: rhodecode/templates/journal/journal.html:96
 msgid "create one now"
 msgstr ""
 
@@ -1764,42 +2571,41 @@ msgstr ""
 msgid "Users administration"
 msgstr ""
 
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/base/base.html:223
+msgid "users"
+msgstr ""
+
 #: rhodecode/templates/admin/users/users.html:23
 msgid "ADD NEW USER"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:33
+#: rhodecode/templates/admin/users/users.html:77
 msgid "username"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:34
-#: rhodecode/templates/branches/branches_data.html:5
-#: rhodecode/templates/tags/tags_data.html:5
-msgid "name"
-msgstr ""
-
-#: rhodecode/templates/admin/users/users.html:35
+#: rhodecode/templates/admin/users/users.html:80
+msgid "firstname"
+msgstr ""
+
+#: rhodecode/templates/admin/users/users.html:81
 msgid "lastname"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:36
+#: rhodecode/templates/admin/users/users.html:82
 msgid "last login"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:37
+#: rhodecode/templates/admin/users/users.html:84
 #: rhodecode/templates/admin/users_groups/users_groups.html:34
 msgid "active"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:39
-#: rhodecode/templates/base/base.html:305
+#: rhodecode/templates/admin/users/users.html:86
+#: rhodecode/templates/base/base.html:226
 msgid "ldap"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:56
-msgid "Confirm to delete this user"
-msgstr ""
-
 #: rhodecode/templates/admin/users_groups/users_group_add.html:5
 msgid "Add users group"
 msgstr ""
@@ -1841,6 +2647,10 @@ msgstr ""
 msgid "Add all elements"
 msgstr ""
 
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:146
+msgid "Group members"
+msgstr ""
+
 #: rhodecode/templates/admin/users_groups/users_groups.html:5
 msgid "Users groups administration"
 msgstr ""
@@ -1853,387 +2663,598 @@ msgstr ""
 msgid "group name"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:32
+#: rhodecode/templates/admin/users_groups/users_groups.html:33
+#: rhodecode/templates/base/root.html:46
+msgid "members"
+msgstr ""
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:45
+#, python-format
+msgid "Confirm to delete this users group: %s"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:41
+msgid "Submit a bug"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:77
+msgid "Login to your account"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:100
 msgid "Forgot password ?"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:57 rhodecode/templates/base/base.html:338
-#: rhodecode/templates/base/base.html:340 rhodecode/templates/base/base.html:342
-msgid "Home"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:61 rhodecode/templates/base/base.html:347
-#: rhodecode/templates/base/base.html:349 rhodecode/templates/base/base.html:351
-#: rhodecode/templates/journal/journal.html:4
-#: rhodecode/templates/journal/journal.html:17
-#: rhodecode/templates/journal/public_journal.html:4
-msgid "Journal"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:66
-msgid "Login"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:68
-msgid "Log Out"
-msgstr ""
-
 #: rhodecode/templates/base/base.html:107
-msgid "Submit a bug"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:141
+msgid "Log In"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:118
+msgid "Inbox"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:122 rhodecode/templates/base/base.html:300
+#: rhodecode/templates/base/base.html:302 rhodecode/templates/base/base.html:304
+#: rhodecode/templates/bookmarks/bookmarks.html:11
+#: rhodecode/templates/branches/branches.html:10
+#: rhodecode/templates/changelog/changelog.html:10
+#: rhodecode/templates/changeset/changeset.html:10
+#: rhodecode/templates/changeset/changeset_range.html:9
+#: rhodecode/templates/compare/compare_diff.html:9
+#: rhodecode/templates/files/file_diff.html:8
+#: rhodecode/templates/files/files.html:8
+#: 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/pullrequests/pullrequest.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
+#: rhodecode/templates/settings/repo_settings.html:9
+#: rhodecode/templates/shortlog/shortlog.html:10
+#: rhodecode/templates/summary/summary.html:8 rhodecode/templates/tags/tags.html:11
+msgid "Home"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:123 rhodecode/templates/base/base.html:309
+#: rhodecode/templates/base/base.html:311 rhodecode/templates/base/base.html:313
+#: rhodecode/templates/journal/journal.html:4
+#: rhodecode/templates/journal/journal.html:21
+#: rhodecode/templates/journal/public_journal.html:4
+msgid "Journal"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:125
+msgid "Log Out"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:144
 msgid "Switch repository"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:143
+#: rhodecode/templates/base/base.html:146
 msgid "Products"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:149
+#: rhodecode/templates/base/base.html:152 rhodecode/templates/base/base.html:182
 msgid "loading..."
 msgstr ""
 
-#: rhodecode/templates/base/base.html:234 rhodecode/templates/base/base.html:236
-#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:158 rhodecode/templates/base/base.html:160
+#: rhodecode/templates/base/base.html:162
+#: rhodecode/templates/data_table/_dt_elements.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:17
+#: rhodecode/templates/data_table/_dt_elements.html:19
+msgid "Summary"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:166 rhodecode/templates/base/base.html:168
+#: rhodecode/templates/base/base.html:170
+#: rhodecode/templates/changelog/changelog.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:23
+#: rhodecode/templates/data_table/_dt_elements.html:25
+#: rhodecode/templates/data_table/_dt_elements.html:27
+msgid "Changelog"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:175 rhodecode/templates/base/base.html:177
+#: rhodecode/templates/base/base.html:179
 msgid "Switch to"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:242
-#: rhodecode/templates/branches/branches.html:13
-msgid "branches"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:249
-#: rhodecode/templates/branches/branches_data.html:52
-msgid "There are no branches yet"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:254
-#: rhodecode/templates/shortlog/shortlog_data.html:10
-#: rhodecode/templates/tags/tags.html:14
-msgid "tags"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:261
-#: rhodecode/templates/tags/tags_data.html:32
-msgid "There are no tags yet"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:277 rhodecode/templates/base/base.html:281
-#: rhodecode/templates/files/files_annotate.html:40
-#: rhodecode/templates/files/files_source.html:11
+#: rhodecode/templates/base/base.html:186 rhodecode/templates/base/base.html:188
+#: rhodecode/templates/base/base.html:190
+#: rhodecode/templates/data_table/_dt_elements.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:33
+#: rhodecode/templates/data_table/_dt_elements.html:35
+msgid "Files"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:195 rhodecode/templates/base/base.html:199
 msgid "Options"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:286 rhodecode/templates/base/base.html:288
-#: rhodecode/templates/base/base.html:306
+#: rhodecode/templates/base/base.html:204 rhodecode/templates/base/base.html:206
+#: rhodecode/templates/base/base.html:227
 msgid "settings"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:292
+#: rhodecode/templates/base/base.html:209
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/forks/fork.html:13
+msgid "fork"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:211
+#: rhodecode/templates/changelog/changelog.html:40
+msgid "Open new pull request"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:213
 msgid "search"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:299
-msgid "journal"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:301
+#: rhodecode/templates/base/base.html:222
 msgid "repositories groups"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:302
-msgid "users"
-msgstr ""
-
-#: rhodecode/templates/base/base.html:303
+#: rhodecode/templates/base/base.html:224
 msgid "users groups"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/base/base.html:225
 msgid "permissions"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:317 rhodecode/templates/base/base.html:319
-#: rhodecode/templates/followers/followers.html:5
+#: rhodecode/templates/base/base.html:238 rhodecode/templates/base/base.html:240
 msgid "Followers"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:325 rhodecode/templates/base/base.html:327
-#: rhodecode/templates/forks/forks.html:5
+#: rhodecode/templates/base/base.html:246 rhodecode/templates/base/base.html:248
 msgid "Forks"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:356 rhodecode/templates/base/base.html:358
-#: rhodecode/templates/base/base.html:360 rhodecode/templates/search/search.html:4
-#: rhodecode/templates/search/search.html:24
-#: rhodecode/templates/search/search.html:46
+#: rhodecode/templates/base/base.html:327 rhodecode/templates/base/base.html:329
+#: rhodecode/templates/base/base.html:331 rhodecode/templates/search/search.html:52
 msgid "Search"
 msgstr ""
 
-#: rhodecode/templates/base/root.html:57
-#: rhodecode/templates/journal/journal.html:48
-#: rhodecode/templates/summary/summary.html:36
+#: rhodecode/templates/base/root.html:42
+msgid "add another comment"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:43
+#: rhodecode/templates/journal/journal.html:120
+#: rhodecode/templates/summary/summary.html:57
 msgid "Stop following this repository"
 msgstr ""
 
-#: rhodecode/templates/base/root.html:66
-#: rhodecode/templates/summary/summary.html:40
+#: rhodecode/templates/base/root.html:44
+#: rhodecode/templates/summary/summary.html:61
 msgid "Start following this repository"
 msgstr ""
 
-#: rhodecode/templates/branches/branches_data.html:4
-#: rhodecode/templates/tags/tags_data.html:4
-msgid "date"
+#: rhodecode/templates/base/root.html:45
+msgid "Group"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:47
+msgid "search truncated"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:48
+msgid "no matching files"
+msgstr ""
+
+#: rhodecode/templates/bookmarks/bookmarks.html:5
+#, python-format
+msgid "%s Bookmarks"
+msgstr ""
+
+#: rhodecode/templates/bookmarks/bookmarks.html:39
+#: rhodecode/templates/bookmarks/bookmarks_data.html:8
+#: rhodecode/templates/branches/branches.html:54
+#: rhodecode/templates/tags/tags.html:39 rhodecode/templates/tags/tags_data.html:8
+msgid "Author"
+msgstr ""
+
+#: rhodecode/templates/branches/branches.html:5
+#, python-format
+msgid "%s Branches"
+msgstr ""
+
+#: rhodecode/templates/branches/branches.html:29
+msgid "Compare branches"
+msgstr ""
+
+#: rhodecode/templates/branches/branches.html:57
+#: rhodecode/templates/compare/compare_diff.html:5
+#: rhodecode/templates/compare/compare_diff.html:13
+msgid "Compare"
 msgstr ""
 
 #: rhodecode/templates/branches/branches_data.html:6
-#: rhodecode/templates/shortlog/shortlog_data.html:7
-#: rhodecode/templates/tags/tags_data.html:6
-msgid "author"
+msgid "name"
+msgstr ""
+
+#: rhodecode/templates/branches/branches_data.html:7
+msgid "date"
 msgstr ""
 
 #: rhodecode/templates/branches/branches_data.html:8
-#: rhodecode/templates/shortlog/shortlog_data.html:11
-#: rhodecode/templates/tags/tags_data.html:8
-msgid "links"
-msgstr ""
-
-#: rhodecode/templates/branches/branches_data.html:23
-#: rhodecode/templates/branches/branches_data.html:43
-#: rhodecode/templates/shortlog/shortlog_data.html:39
-#: rhodecode/templates/tags/tags_data.html:24
-msgid "changeset"
-msgstr ""
-
-#: rhodecode/templates/branches/branches_data.html:25
-#: rhodecode/templates/branches/branches_data.html:45
-#: rhodecode/templates/files/files.html:12
-#: rhodecode/templates/shortlog/shortlog_data.html:41
-#: rhodecode/templates/summary/summary.html:233
-#: rhodecode/templates/tags/tags_data.html:26
-msgid "files"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "showing "
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "out of"
+#: rhodecode/templates/shortlog/shortlog_data.html:8
+msgid "author"
+msgstr ""
+
+#: rhodecode/templates/branches/branches_data.html:9
+#: rhodecode/templates/shortlog/shortlog_data.html:5
+msgid "revision"
+msgstr ""
+
+#: rhodecode/templates/branches/branches_data.html:10
+msgid "compare"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:6
+#, python-format
+msgid "%s Changelog"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:15
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:19
+#, python-format
+msgid "compare fork with %s"
 msgstr ""
 
 #: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:21
+msgid "Compare fork"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:46
 msgid "Show"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:50
-#: rhodecode/templates/changeset/changeset.html:42
-#: rhodecode/templates/summary/summary.html:609
-msgid "commit"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:63
-msgid "Affected number of files, click to show more details"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:67
-#: rhodecode/templates/changeset/changeset.html:66
-msgid "merge"
-msgstr ""
-
 #: rhodecode/templates/changelog/changelog.html:72
-#: rhodecode/templates/changeset/changeset.html:72
+#: rhodecode/templates/summary/summary.html:364
+msgid "show more"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:76
+msgid "Affected number of files, click to show more details"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:89
+#: rhodecode/templates/changeset/changeset.html:38
+#: rhodecode/templates/changeset/changeset_file_comment.html:20
+#: rhodecode/templates/changeset/changeset_range.html:46
+msgid "Changeset status"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:92
+msgid "Click to open associated pull request"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:102
+#: rhodecode/templates/changeset/changeset.html:78
 msgid "Parent"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:77
-#: rhodecode/templates/changeset/changeset.html:77
+#: rhodecode/templates/changelog/changelog.html:108
+#: rhodecode/templates/changeset/changeset.html:84
 msgid "No parents"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:82
-#: rhodecode/templates/changeset/changeset.html:80
+#: rhodecode/templates/changelog/changelog.html:113
+#: rhodecode/templates/changeset/changeset.html:88
+msgid "merge"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:116
+#: rhodecode/templates/changeset/changeset.html:91
 #: rhodecode/templates/files/files.html:29
-#: rhodecode/templates/files/files_annotate.html:25
+#: rhodecode/templates/files/files_add.html:33
 #: rhodecode/templates/files/files_edit.html:33
 #: rhodecode/templates/shortlog/shortlog_data.html:9
 msgid "branch"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:86
-#: rhodecode/templates/changeset/changeset.html:83
+#: rhodecode/templates/changelog/changelog.html:122
+msgid "bookmark"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:128
+#: rhodecode/templates/changeset/changeset.html:96
 msgid "tag"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:122
+#: rhodecode/templates/changelog/changelog.html:164
 msgid "Show selected changes __S -> __E"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:172
-#: rhodecode/templates/shortlog/shortlog_data.html:61
+#: rhodecode/templates/changelog/changelog.html:255
 msgid "There are no changes yet"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog_details.html:2
-#: rhodecode/templates/changeset/changeset.html:55
-msgid "removed"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog_details.html:3
-#: rhodecode/templates/changeset/changeset.html:56
-msgid "changed"
-msgstr ""
-
 #: rhodecode/templates/changelog/changelog_details.html:4
-#: rhodecode/templates/changeset/changeset.html:57
-msgid "added"
+#: rhodecode/templates/changeset/changeset.html:66
+msgid "removed"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog_details.html:5
+#: rhodecode/templates/changeset/changeset.html:67
+msgid "changed"
 msgstr ""
 
 #: rhodecode/templates/changelog/changelog_details.html:6
-#: rhodecode/templates/changelog/changelog_details.html:7
+#: rhodecode/templates/changeset/changeset.html:68
+msgid "added"
+msgstr ""
+
 #: rhodecode/templates/changelog/changelog_details.html:8
-#: rhodecode/templates/changeset/changeset.html:59
-#: rhodecode/templates/changeset/changeset.html:60
-#: rhodecode/templates/changeset/changeset.html:61
+#: rhodecode/templates/changelog/changelog_details.html:9
+#: rhodecode/templates/changelog/changelog_details.html:10
+#: rhodecode/templates/changeset/changeset.html:70
+#: rhodecode/templates/changeset/changeset.html:71
+#: rhodecode/templates/changeset/changeset.html:72
 #, python-format
 msgid "affected %s files"
 msgstr ""
 
 #: rhodecode/templates/changeset/changeset.html:6
+#, python-format
+msgid "%s Changeset"
+msgstr ""
+
 #: rhodecode/templates/changeset/changeset.html:14
-#: rhodecode/templates/changeset/changeset.html:31
 msgid "Changeset"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:32
-#: rhodecode/templates/changeset/changeset.html:121
-#: rhodecode/templates/changeset/changeset_range.html:78
-#: rhodecode/templates/files/file_diff.html:32
-#: rhodecode/templates/files/file_diff.html:42
+#: rhodecode/templates/changeset/changeset.html:43
+#: rhodecode/templates/changeset/diff_block.html:20
 msgid "raw diff"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:34
-#: rhodecode/templates/changeset/changeset.html:123
-#: rhodecode/templates/changeset/changeset_range.html:80
-#: rhodecode/templates/files/file_diff.html:34
+#: rhodecode/templates/changeset/changeset.html:44
+#: rhodecode/templates/changeset/diff_block.html:21
 msgid "download diff"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:90
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
 #, python-format
-msgid "%s files affected with %s additions and %s deletions."
-msgstr ""
-
-#: rhodecode/templates/changeset/changeset.html:101
-msgid "Changeset was too big and was cut off..."
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "(%d inline)"
+msgid_plural "(%d inline)"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/changeset/changeset.html:103
+#, python-format
+msgid "%s files affected with %s insertions and %s deletions:"
 msgstr ""
 
 #: rhodecode/templates/changeset/changeset.html:119
-#: rhodecode/templates/changeset/changeset_range.html:76
-#: rhodecode/templates/files/file_diff.html:30
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:42
+msgid "Submitting..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:45
+msgid "Commenting on line {1}."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:46
+#: rhodecode/templates/changeset/changeset_file_comment.html:121
+#, python-format
+msgid "Comments parsed using %s syntax with %s support."
+msgstr ""
+
+#: 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 ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:59
+#: rhodecode/templates/changeset/changeset_file_comment.html:138
+msgid "Comment"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:60
+#: rhodecode/templates/changeset/changeset_file_comment.html:71
+msgid "Hide"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "You need to be logged in to comment."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "Login now"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:118
+msgid "Leave a comment"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "Check this to change current status of code-review for this changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "change status"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:140
+msgid "Comment and close"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:5
+#, python-format
+msgid "%s Changesets"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/templates/compare/compare_diff.html:29
+msgid "Compare View"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:54
+#: rhodecode/templates/compare/compare_diff.html:41
+#: rhodecode/templates/pullrequests/pullrequest_show.html:69
+msgid "Files affected"
+msgstr ""
+
+#: rhodecode/templates/changeset/diff_block.html:19
 msgid "diff"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:132
-#: rhodecode/templates/changeset/changeset_range.html:89
-msgid "No changes in this file"
-msgstr ""
-
-#: rhodecode/templates/changeset/changeset_range.html:30
-msgid "Compare View"
-msgstr ""
-
-#: rhodecode/templates/changeset/changeset_range.html:52
-msgid "Files affected"
-msgstr ""
-
-#: rhodecode/templates/errors/error_document.html:44
+#: rhodecode/templates/changeset/diff_block.html:27
+msgid "show inline comments"
+msgstr ""
+
+#: rhodecode/templates/compare/compare_cs.html:5
+msgid "No changesets"
+msgstr ""
+
+#: rhodecode/templates/compare/compare_diff.html:37
+msgid "Outgoing changesets"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:39
+#: rhodecode/templates/data_table/_dt_elements.html:41
+#: rhodecode/templates/data_table/_dt_elements.html:43
+msgid "Fork"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:60
+#: rhodecode/templates/journal/journal.html:126
+#: rhodecode/templates/summary/summary.html:68
+msgid "Mercurial repository"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:62
+#: rhodecode/templates/journal/journal.html:128
+#: rhodecode/templates/summary/summary.html:71
+msgid "Git repository"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:69
+#: rhodecode/templates/journal/journal.html:134
+#: rhodecode/templates/summary/summary.html:78
+msgid "public repository"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/summary/summary.html:87
+#: rhodecode/templates/summary/summary.html:88
+msgid "Fork of"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:92
+msgid "No changesets yet"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:104
+#, python-format
+msgid "Confirm to delete this user: %s"
+msgstr ""
+
+#: rhodecode/templates/email_templates/main.html:8
+msgid "This is an notification from RhodeCode."
+msgstr ""
+
+#: rhodecode/templates/errors/error_document.html:46
 #, python-format
 msgid "You will be redirected to %s in %s seconds"
 msgstr ""
 
 #: rhodecode/templates/files/file_diff.html:4
+#, python-format
+msgid "%s File diff"
+msgstr ""
+
 #: rhodecode/templates/files/file_diff.html:12
 msgid "File diff"
 msgstr ""
 
-#: rhodecode/templates/files/file_diff.html:42
-msgid "Diff is to big to display"
-msgstr ""
-
-#: rhodecode/templates/files/files.html:37
-#: rhodecode/templates/files/files_annotate.html:31
+#: rhodecode/templates/files/files.html:4 rhodecode/templates/files/files.html:72
+#, python-format
+msgid "%s files"
+msgstr ""
+
+#: rhodecode/templates/files/files.html:12
+#: rhodecode/templates/summary/summary.html:340
+msgid "files"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:4
+#: rhodecode/templates/files/files_edit.html:4
+#, python-format
+msgid "%s Edit file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:19
+msgid "add file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:40
+msgid "Add new file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:45
+msgid "File Name"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:58
+msgid "or"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:54
+msgid "Upload file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:58
+msgid "Create new file"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:63
 #: rhodecode/templates/files/files_edit.html:39
+#: rhodecode/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr ""
 
-#: rhodecode/templates/files/files.html:46
-msgid "Go back"
-msgstr ""
-
-#: rhodecode/templates/files/files.html:47
-msgid "No files at given path"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:4
-msgid "File annotate"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:12
-msgid "annotate"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:33
-#: rhodecode/templates/files/files_browser.html:160
-#: rhodecode/templates/files/files_source.html:2
-msgid "Revision"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:36
-#: rhodecode/templates/files/files_browser.html:158
-#: rhodecode/templates/files/files_source.html:7
-msgid "Size"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:38
-#: rhodecode/templates/files/files_browser.html:159
-#: rhodecode/templates/files/files_source.html:9
-msgid "Mimetype"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:41
-msgid "show source"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:43
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:14
-#: rhodecode/templates/files/files_source.html:51
-msgid "show as raw"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:45
-#: rhodecode/templates/files/files_source.html:16
-msgid "download as raw"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:54
-#: rhodecode/templates/files/files_source.html:25
-msgid "History"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:73
-#: rhodecode/templates/files/files_source.html:46
-#, python-format
-msgid "Binary file (%s)"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:51
-msgid "File is too big to display"
+#: rhodecode/templates/files/files_add.html:67
+msgid "use / to separate directories"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:77
+#: rhodecode/templates/files/files_edit.html:63
+#: rhodecode/templates/shortlog/shortlog_data.html:6
+msgid "commit message"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:81
+#: rhodecode/templates/files/files_edit.html:67
+msgid "Commit changes"
 msgstr ""
 
 #: rhodecode/templates/files/files_browser.html:13
@@ -2256,57 +3277,160 @@ msgstr ""
 msgid "search file list"
 msgstr ""
 
-#: rhodecode/templates/files/files_browser.html:32
+#: rhodecode/templates/files/files_browser.html:31
+#: rhodecode/templates/shortlog/shortlog_data.html:65
+msgid "add new file"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:35
 msgid "Loading file list..."
 msgstr ""
 
-#: rhodecode/templates/files/files_browser.html:111
-msgid "search truncated"
-msgstr ""
-
-#: rhodecode/templates/files/files_browser.html:122
-msgid "no matching files"
-msgstr ""
-
-#: rhodecode/templates/files/files_browser.html:161
+#: rhodecode/templates/files/files_browser.html:48
+msgid "Size"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:49
+msgid "Mimetype"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:50
+msgid "Last Revision"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:51
 msgid "Last modified"
 msgstr ""
 
-#: rhodecode/templates/files/files_browser.html:162
+#: rhodecode/templates/files/files_browser.html:52
 msgid "Last commiter"
 msgstr ""
 
-#: rhodecode/templates/files/files_edit.html:4
-msgid "Edit file"
-msgstr ""
-
 #: rhodecode/templates/files/files_edit.html:19
 msgid "edit file"
 msgstr ""
 
-#: rhodecode/templates/files/files_edit.html:45
-#: rhodecode/templates/shortlog/shortlog_data.html:5
-msgid "commit message"
+#: rhodecode/templates/files/files_edit.html:49
+#: rhodecode/templates/files/files_source.html:38
+msgid "show annotation"
+msgstr ""
+
+#: rhodecode/templates/files/files_edit.html:50
+#: rhodecode/templates/files/files_source.html:40
+#: rhodecode/templates/files/files_source.html:68
+msgid "show as raw"
 msgstr ""
 
 #: rhodecode/templates/files/files_edit.html:51
-msgid "Commit changes"
-msgstr ""
-
-#: rhodecode/templates/files/files_source.html:12
-msgid "show annotation"
-msgstr ""
-
-#: rhodecode/templates/files/files_source.html:153
+#: rhodecode/templates/files/files_source.html:41
+msgid "download as raw"
+msgstr ""
+
+#: rhodecode/templates/files/files_edit.html:54
+msgid "source"
+msgstr ""
+
+#: rhodecode/templates/files/files_edit.html:59
+msgid "Editing file"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:2
+msgid "History"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:9
+msgid "diff to revision"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:10
+msgid "show at revision"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:14
+#, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/files/files_source.html:36
+msgid "show source"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:59
+#, python-format
+msgid "Binary file (%s)"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:68
+msgid "File is too big to display"
+msgstr ""
+
+#: rhodecode/templates/files/files_source.html:124
 msgid "Selection link"
 msgstr ""
 
+#: rhodecode/templates/files/files_ypjax.html:5
+msgid "annotation"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:15
+msgid "Go back"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:16
+msgid "No files at given path"
+msgstr ""
+
+#: rhodecode/templates/followers/followers.html:5
+#, python-format
+msgid "%s Followers"
+msgstr ""
+
 #: rhodecode/templates/followers/followers.html:13
 msgid "followers"
 msgstr ""
 
 #: rhodecode/templates/followers/followers_data.html:12
-msgid "Started following"
+msgid "Started following -"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:5
+#, python-format
+msgid "%s Fork"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:31
+msgid "Fork name"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:68
+msgid "Private"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:77
+msgid "Copy permissions"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:81
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:86
+msgid "Update after clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:90
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:94
+msgid "fork this repository"
+msgstr ""
+
+#: rhodecode/templates/forks/forks.html:5
+#, python-format
+msgid "%s Forks"
 msgstr ""
 
 #: rhodecode/templates/forks/forks.html:13
@@ -2317,184 +3441,395 @@ msgstr ""
 msgid "forked"
 msgstr ""
 
-#: rhodecode/templates/forks/forks_data.html:34
+#: rhodecode/templates/forks/forks_data.html:38
 msgid "There are no forks yet"
 msgstr ""
 
-#: rhodecode/templates/journal/journal.html:34
-msgid "Following"
-msgstr ""
-
-#: rhodecode/templates/journal/journal.html:41
-msgid "following user"
+#: rhodecode/templates/journal/journal.html:13
+msgid "ATOM journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:14
+msgid "RSS journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:24
+#: rhodecode/templates/pullrequests/pullrequest.html:27
+msgid "Refresh"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:27
+#: rhodecode/templates/journal/public_journal.html:24
+msgid "RSS feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:30
+#: rhodecode/templates/journal/public_journal.html:27
+msgid "ATOM feed"
 msgstr ""
 
 #: rhodecode/templates/journal/journal.html:41
+msgid "Watched"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:46
+msgid "ADD"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "following user"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:114
 msgid "user"
 msgstr ""
 
-#: rhodecode/templates/journal/journal.html:65
+#: rhodecode/templates/journal/journal.html:147
 msgid "You are not following any users or repositories"
 msgstr ""
 
-#: rhodecode/templates/journal/journal_data.html:46
+#: rhodecode/templates/journal/journal_data.html:47
 msgid "No entries yet"
 msgstr ""
 
-#: rhodecode/templates/journal/public_journal.html:17
+#: rhodecode/templates/journal/public_journal.html:13
+msgid "ATOM public journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/public_journal.html:14
+msgid "RSS public journal feed"
+msgstr ""
+
+#: rhodecode/templates/journal/public_journal.html:21
 msgid "Public Journal"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:7
-#: rhodecode/templates/search/search.html:26
-msgid "in repository: "
-msgstr ""
-
-#: rhodecode/templates/search/search.html:9
-#: rhodecode/templates/search/search.html:28
-msgid "in all repositories"
-msgstr ""
-
-#: rhodecode/templates/search/search.html:42
+#: rhodecode/templates/pullrequests/pullrequest.html:4
+#: rhodecode/templates/pullrequests/pullrequest.html:12
+msgid "New pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:28
+msgid "refresh overview"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:66
+msgid "Detailed compare view"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:70
+#: rhodecode/templates/pullrequests/pullrequest_show.html:82
+msgid "Pull request reviewers"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:79
+#: rhodecode/templates/pullrequests/pullrequest_show.html:94
+msgid "owner"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:91
+#: rhodecode/templates/pullrequests/pullrequest_show.html:109
+msgid "Add reviewer to this pull request."
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:97
+msgid "Create new pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:106
+#: rhodecode/templates/pullrequests/pullrequest_show.html:25
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
+msgid "Title"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:115
+msgid "description"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:123
+msgid "Send pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:23
+#, python-format
+msgid "Closed %s"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:31
+msgid "Status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:36
+msgid "Pull request status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:44
+msgid "Still not reviewed by"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:47
+#, python-format
+msgid "%d reviewer"
+msgid_plural "%d reviewers"
+msgstr[0] ""
+msgstr[1] ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:54
+msgid "Created on"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:61
+msgid "Compare view"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:65
+msgid "Incoming changesets"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
+msgid "all pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
+msgid "All pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
+msgid "Closed"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:6
+#, python-format
+msgid "Search \"%s\" in repository: %s"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:8
+#, python-format
+msgid "Search \"%s\" in all repositories"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:12
+#: rhodecode/templates/search/search.html:32
+#, python-format
+msgid "Search in repository: %s"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:14
+#: rhodecode/templates/search/search.html:34
+msgid "Search in all repositories"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:48
 msgid "Search term"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:54
+#: rhodecode/templates/search/search.html:60
 msgid "Search in"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:57
+#: rhodecode/templates/search/search.html:63
 msgid "File contents"
 msgstr ""
 
-#: rhodecode/templates/search/search.html:59
+#: rhodecode/templates/search/search.html:64
+msgid "Commit messages"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:65
 msgid "File names"
 msgstr ""
 
-#: rhodecode/templates/search/search_content.html:20
+#: rhodecode/templates/search/search_commit.html:35
+#: rhodecode/templates/search/search_content.html:21
 #: rhodecode/templates/search/search_path.html:15
 msgid "Permission denied"
 msgstr ""
 
-#: rhodecode/templates/settings/repo_fork.html:5
-msgid "Fork"
-msgstr ""
-
-#: rhodecode/templates/settings/repo_fork.html:31
-msgid "Fork name"
-msgstr ""
-
-#: rhodecode/templates/settings/repo_fork.html:55
-msgid "fork this repository"
+#: rhodecode/templates/settings/repo_settings.html:5
+#, python-format
+msgid "%s Settings"
 msgstr ""
 
 #: rhodecode/templates/shortlog/shortlog.html:5
-#: rhodecode/templates/summary/summary.html:666
-msgid "Shortlog"
+#, python-format
+msgid "%s Shortlog"
 msgstr ""
 
 #: rhodecode/templates/shortlog/shortlog.html:14
 msgid "shortlog"
 msgstr ""
 
-#: rhodecode/templates/shortlog/shortlog_data.html:6
+#: rhodecode/templates/shortlog/shortlog_data.html:7
 msgid "age"
 msgstr ""
 
+#: rhodecode/templates/shortlog/shortlog_data.html:18
+msgid "No commit message"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:62
+msgid "Add or upload files directly via RhodeCode"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:71
+msgid "Push new repo"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:79
+msgid "Existing repository?"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:4
+#, python-format
+msgid "%s Summary"
+msgstr ""
+
 #: rhodecode/templates/summary/summary.html:12
 msgid "summary"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:79
+#: rhodecode/templates/summary/summary.html:20
+#, python-format
+msgid "repo %s ATOM feed"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:21
+#, python-format
+msgid "repo %s RSS feed"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:49
+#: rhodecode/templates/summary/summary.html:52
+msgid "ATOM"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:82
+#, python-format
+msgid "Non changable ID %s"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:87
+msgid "public"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:95
 msgid "remote clone"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:121
-msgid "by"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:128
+#: rhodecode/templates/summary/summary.html:116
+msgid "Contact"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:130
 msgid "Clone url"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:137
-msgid "Trending source files"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:146
-msgid "Download"
+#: rhodecode/templates/summary/summary.html:133
+msgid "Show by Name"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:134
+msgid "Show by ID"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:142
+msgid "Trending files"
 msgstr ""
 
 #: rhodecode/templates/summary/summary.html:150
-msgid "There are no downloads yet"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:152
-msgid "Downloads are disabled for this repository"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:154
-#: rhodecode/templates/summary/summary.html:320
+#: rhodecode/templates/summary/summary.html:166
+#: rhodecode/templates/summary/summary.html:194
 msgid "enable"
 msgstr ""
 
+#: rhodecode/templates/summary/summary.html:158
+msgid "Download"
+msgstr ""
+
 #: rhodecode/templates/summary/summary.html:162
-#: rhodecode/templates/summary/summary.html:297
+msgid "There are no downloads yet"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:164
+msgid "Downloads are disabled for this repository"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:170
+msgid "Download as zip"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:186
+msgid "Commit activity by day / author"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:197
+msgid "Stats gathered: "
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:218
+msgid "Shortlog"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:220
+msgid "Quick start"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:233
+#, python-format
+msgid "Readme file at revision '%s'"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:236
+msgid "Permalink to this readme"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:293
 #, python-format
 msgid "Download %s as %s"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:168
-msgid "Check this to download archive with subrepos"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:168
-msgid "with subrepos"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:176
-msgid "Feeds"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:257
-#: rhodecode/templates/summary/summary.html:684
-#: rhodecode/templates/summary/summary.html:695
-msgid "show more"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:312
-msgid "Commit activity by day / author"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:324
-msgid "Loaded in"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:603
+#: rhodecode/templates/summary/summary.html:650
 msgid "commits"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:604
+#: rhodecode/templates/summary/summary.html:651
 msgid "files added"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:605
+#: rhodecode/templates/summary/summary.html:652
 msgid "files changed"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:606
+#: rhodecode/templates/summary/summary.html:653
 msgid "files removed"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:610
+#: rhodecode/templates/summary/summary.html:656
+msgid "commit"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:657
 msgid "file added"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:611
+#: rhodecode/templates/summary/summary.html:658
 msgid "file changed"
 msgstr ""
 
-#: rhodecode/templates/summary/summary.html:612
+#: rhodecode/templates/summary/summary.html:659
 msgid "file removed"
 msgstr ""
 
+#: rhodecode/templates/tags/tags.html:5
+#, python-format
+msgid "%s Tags"
+msgstr ""
+
diff --git a/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.mo b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.mo
new file mode 100644
index 0000000000000000000000000000000000000000..341b41274f27d4b8495c51072b2c3252f2705a68
GIT binary patch
literal 23719
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

diff --git a/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po
new file mode 100644
--- /dev/null
+++ b/rhodecode/i18n/zh_CN/LC_MESSAGES/rhodecode.po
@@ -0,0 +1,4022 @@
+# Chinese (China) translations for RhodeCode.
+# Copyright (C) 2011 ORGANIZATION
+# This file is distributed under the same license as the RhodeCode project.
+# FIRST AUTHOR , 2011.
+# mikespook , 2012.
+msgid ""
+msgstr ""
+"Project-Id-Version: RhodeCode 1.2.0\n"
+"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
+"POT-Creation-Date: 2012-09-02 20:30+0200\n"
+"PO-Revision-Date: 2012-04-05 17:37+0800\n"
+"Last-Translator: mikespook \n"
+"Language-Team: mikespook\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"
+
+#: rhodecode/controllers/changelog.py:94
+#, fuzzy
+msgid "All Branches"
+msgstr "分支"
+
+#: rhodecode/controllers/changeset.py:83
+msgid "show white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+msgid "ignore white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:157
+#, fuzzy, python-format
+msgid "%s line context"
+msgstr "文件内容"
+
+#: rhodecode/controllers/changeset.py:333
+#: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
+msgid "binary file"
+msgstr "二进制文件"
+
+#: rhodecode/controllers/changeset.py:408
+msgid ""
+"Changing status on a changeset associated witha closed pull request is "
+"not allowed"
+msgstr ""
+
+#: rhodecode/controllers/compare.py:69
+#, fuzzy
+msgid "There are no changesets yet"
+msgstr "没有任何变更"
+
+#: rhodecode/controllers/error.py:69
+msgid "Home page"
+msgstr "主页"
+
+#: rhodecode/controllers/error.py:98
+msgid "The request could not be understood by the server due to malformed syntax."
+msgstr "由于错误的语法,服务器无法对请求进行响应。"
+
+#: rhodecode/controllers/error.py:101
+msgid "Unauthorized access to resource"
+msgstr "未授权的资源访问"
+
+#: rhodecode/controllers/error.py:103
+msgid "You don't have permission to view this page"
+msgstr "无权访问该页面"
+
+#: rhodecode/controllers/error.py:105
+msgid "The resource could not be found"
+msgstr "资源未找到"
+
+#: rhodecode/controllers/error.py:107
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr "服务进入非预期的混乱状态,这会阻止它对请求进行响应。"
+
+#: rhodecode/controllers/feed.py:49
+#, python-format
+msgid "Changes on %s repository"
+msgstr "%s 库的修改"
+
+#: rhodecode/controllers/feed.py:50
+#, python-format
+msgid "%s %s feed"
+msgstr "%s %s 订阅"
+
+#: rhodecode/controllers/feed.py:75
+#, fuzzy
+msgid "commited on"
+msgstr "提交"
+
+#: rhodecode/controllers/files.py:84
+#, fuzzy
+msgid "click here to add new file"
+msgstr "添加新用户"
+
+#: rhodecode/controllers/files.py:85
+#, fuzzy, python-format
+msgid "There are no files yet %s"
+msgstr "尚无文件"
+
+#: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
+#, python-format
+msgid "This repository is has been locked by %s on %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:266
+#, python-format
+msgid "Edited %s via RhodeCode"
+msgstr "通过 RhodeCode 修改了 %s"
+
+#: rhodecode/controllers/files.py:271
+msgid "No changes"
+msgstr "无变更"
+
+#: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
+#, python-format
+msgid "Successfully committed to %s"
+msgstr "成功提交到 %s"
+
+#: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
+msgid "Error occurred during commit"
+msgstr "提交时发生错误"
+
+#: rhodecode/controllers/files.py:318
+#, fuzzy, python-format
+msgid "Added %s via RhodeCode"
+msgstr "通过 RhodeCode 修改了 %s"
+
+#: rhodecode/controllers/files.py:332
+#, fuzzy
+msgid "No content"
+msgstr "文件内容"
+
+#: rhodecode/controllers/files.py:336
+#, fuzzy
+msgid "No filename"
+msgstr "文件名"
+
+#: rhodecode/controllers/files.py:378
+msgid "downloads disabled"
+msgstr "禁止下载"
+
+#: rhodecode/controllers/files.py:389
+#, python-format
+msgid "Unknown revision %s"
+msgstr "未知版本 %s"
+
+#: rhodecode/controllers/files.py:391
+msgid "Empty repository"
+msgstr "空版本库"
+
+#: rhodecode/controllers/files.py:393
+msgid "Unknown archive type"
+msgstr "未知包类型"
+
+#: rhodecode/controllers/files.py:494
+#: rhodecode/templates/changeset/changeset_range.html:13
+#: rhodecode/templates/changeset/changeset_range.html:31
+msgid "Changesets"
+msgstr "变更集"
+
+#: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
+#: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
+msgid "Branches"
+msgstr "分支"
+
+#: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
+#: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
+msgid "Tags"
+msgstr "标签"
+
+#: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
+#, 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 ""
+
+#: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
+#, python-format
+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 ""
+
+#: rhodecode/controllers/forks.py:167
+#, python-format
+msgid "forked %s repository as %s"
+msgstr "版本库 %s 被分支到 %s"
+
+#: rhodecode/controllers/forks.py:181
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr ""
+
+#: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
+#, fuzzy
+msgid "public journal"
+msgstr "公共日志"
+
+#: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
+#: rhodecode/templates/base/base.html:220
+msgid "journal"
+msgstr "日志"
+
+#: rhodecode/controllers/login.py:143
+msgid "You have successfully registered into rhodecode"
+msgstr "成功注册到 rhodecode"
+
+#: rhodecode/controllers/login.py:164
+msgid "Your password reset link was sent"
+msgstr "密码重置链接已经发送"
+
+#: rhodecode/controllers/login.py:184
+msgid ""
+"Your password reset was successful, new password has been sent to your "
+"email"
+msgstr "密码已经成功重置,新密码已经发送到你的邮箱"
+
+#: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
+msgid "Bookmarks"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:158
+msgid "Pull request requires a title with min. 3 chars"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:160
+#, fuzzy
+msgid "error during creation of pull request"
+msgstr "提交时发生错误"
+
+#: rhodecode/controllers/pullrequests.py:181
+#, fuzzy
+msgid "Successfully opened new pull request"
+msgstr "用户删除成功"
+
+#: rhodecode/controllers/pullrequests.py:184
+#, fuzzy
+msgid "Error occurred during sending pull request"
+msgstr "提交时发生错误"
+
+#: rhodecode/controllers/pullrequests.py:217
+#, fuzzy
+msgid "Successfully deleted pull request"
+msgstr "用户删除成功"
+
+#: rhodecode/controllers/search.py:131
+msgid "Invalid search query. Try quoting it."
+msgstr "错误的搜索。请尝试用引号包含它。"
+
+#: rhodecode/controllers/search.py:136
+msgid "There is no index to search in. Please run whoosh indexer"
+msgstr "没有索引用于搜索。请运行 whoosh 索引器"
+
+#: rhodecode/controllers/search.py:140
+msgid "An error occurred during this search operation"
+msgstr "在搜索操作中发生异常"
+
+#: rhodecode/controllers/settings.py:107
+#: rhodecode/controllers/admin/repos.py:266
+#, python-format
+msgid "Repository %s updated successfully"
+msgstr "版本库 %s 成功更新"
+
+#: rhodecode/controllers/settings.py:125
+#: rhodecode/controllers/admin/repos.py:284
+#, python-format
+msgid "error occurred during update of repository %s"
+msgstr ""
+
+#: rhodecode/controllers/settings.py:143
+#: rhodecode/controllers/admin/repos.py:302
+#, python-format
+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 ""
+
+#: rhodecode/controllers/settings.py:155
+#: rhodecode/controllers/admin/repos.py:314
+#, python-format
+msgid "deleted repository %s"
+msgstr "已经删除版本库 %s"
+
+#: rhodecode/controllers/settings.py:159
+#: rhodecode/controllers/admin/repos.py:324
+#: rhodecode/controllers/admin/repos.py:330
+#, python-format
+msgid "An error occurred during deletion of %s"
+msgstr ""
+
+#: rhodecode/controllers/summary.py:138
+msgid "No data loaded yet"
+msgstr ""
+
+#: rhodecode/controllers/summary.py:142
+#: rhodecode/templates/summary/summary.html:148
+msgid "Statistics are disabled for this repository"
+msgstr "该版本库统计功能已经禁用"
+
+#: rhodecode/controllers/admin/ldap_settings.py:50
+msgid "BASE"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:51
+msgid "ONELEVEL"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:52
+msgid "SUBTREE"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:56
+msgid "NEVER"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:57
+msgid "ALLOW"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:58
+msgid "TRY"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:59
+msgid "DEMAND"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:60
+msgid "HARD"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:64
+msgid "No encryption"
+msgstr "未加密"
+
+#: rhodecode/controllers/admin/ldap_settings.py:65
+msgid "LDAPS connection"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:66
+msgid "START_TLS on LDAP connection"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:126
+msgid "Ldap settings updated successfully"
+msgstr "LDAP 设置已经成功更新"
+
+#: rhodecode/controllers/admin/ldap_settings.py:130
+msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
+msgstr "无法启用 LDAP。库“python-ldap”缺失。"
+
+#: rhodecode/controllers/admin/ldap_settings.py:147
+msgid "error occurred during update of ldap settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/permissions.py:59
+msgid "None"
+msgstr "无"
+
+#: rhodecode/controllers/admin/permissions.py:60
+msgid "Read"
+msgstr "读"
+
+#: rhodecode/controllers/admin/permissions.py:61
+msgid "Write"
+msgstr "写"
+
+#: rhodecode/controllers/admin/permissions.py:62
+#: rhodecode/templates/admin/ldap/ldap.html:9
+#: rhodecode/templates/admin/permissions/permissions.html:9
+#: rhodecode/templates/admin/repos/repo_add.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:9
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
+#: rhodecode/templates/admin/settings/hooks.html:9
+#: rhodecode/templates/admin/settings/settings.html:9
+#: rhodecode/templates/admin/users/user_add.html:8
+#: rhodecode/templates/admin/users/user_edit.html:9
+#: rhodecode/templates/admin/users/user_edit.html:122
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/admin/users_groups/users_group_add.html:8
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:9
+#: rhodecode/templates/admin/users_groups/users_groups.html:9
+#: rhodecode/templates/base/base.html:197
+#: rhodecode/templates/base/base.html:337
+#: rhodecode/templates/base/base.html:339
+#: rhodecode/templates/base/base.html:341
+msgid "Admin"
+msgstr "管理"
+
+#: rhodecode/controllers/admin/permissions.py:65
+msgid "disabled"
+msgstr "禁用"
+
+#: rhodecode/controllers/admin/permissions.py:67
+msgid "allowed with manual account activation"
+msgstr "允许手工启用帐号"
+
+#: rhodecode/controllers/admin/permissions.py:69
+msgid "allowed with automatic account activation"
+msgstr "允许自动启用帐号"
+
+#: rhodecode/controllers/admin/permissions.py:71
+#: rhodecode/controllers/admin/permissions.py:74
+msgid "Disabled"
+msgstr "停用"
+
+#: rhodecode/controllers/admin/permissions.py:72
+#: rhodecode/controllers/admin/permissions.py:75
+msgid "Enabled"
+msgstr "启用"
+
+#: rhodecode/controllers/admin/permissions.py:116
+msgid "Default permissions updated successfully"
+msgstr "成功更新默认权限"
+
+#: rhodecode/controllers/admin/permissions.py:130
+msgid "error occurred during update of permissions"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:123
+msgid "--REMOVE FORK--"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:192
+#, python-format
+msgid "created repository %s from %s"
+msgstr "新版本库 %s 基于 %s 建立。"
+
+#: rhodecode/controllers/admin/repos.py:196
+#, python-format
+msgid "created repository %s"
+msgstr "建立版本库 %s"
+
+#: rhodecode/controllers/admin/repos.py:227
+#, python-format
+msgid "error occurred during creation of repository %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:319
+#, python-format
+msgid "Cannot delete %s it still contains attached forks"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:348
+msgid "An error occurred during deletion of repository user"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:367
+msgid "An error occurred during deletion of repository users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:385
+msgid "An error occurred during deletion of repository stats"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:402
+msgid "An error occurred during cache invalidation"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:422
+#, fuzzy
+msgid "An error occurred during unlocking"
+msgstr "在搜索操作中发生异常"
+
+#: rhodecode/controllers/admin/repos.py:442
+msgid "Updated repository visibility in public journal"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:446
+msgid "An error occurred during setting this repository in public journal"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
+msgid "Token mismatch"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:464
+msgid "Pulled from remote location"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:466
+msgid "An error occurred during pull from remote location"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:482
+msgid "Nothing"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:484
+#, fuzzy, python-format
+msgid "Marked repo %s as fork of %s"
+msgstr "新版本库 %s 基于 %s 建立。"
+
+#: rhodecode/controllers/admin/repos.py:488
+#, fuzzy
+msgid "An error occurred during this operation"
+msgstr "在搜索操作中发生异常"
+
+#: rhodecode/controllers/admin/repos_groups.py:116
+#, python-format
+msgid "created repos group %s"
+msgstr "建立版本库组 %s"
+
+#: rhodecode/controllers/admin/repos_groups.py:129
+#, python-format
+msgid "error occurred during creation of repos group %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:163
+#, python-format
+msgid "updated repos group %s"
+msgstr "更新版本库组 %s"
+
+#: rhodecode/controllers/admin/repos_groups.py:176
+#, python-format
+msgid "error occurred during update of repos group %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:194
+#, python-format
+msgid "This group contains %s repositores and cannot be deleted"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:202
+#, python-format
+msgid "removed repos group %s"
+msgstr "移除版本库组 %s"
+
+#: rhodecode/controllers/admin/repos_groups.py:208
+msgid "Cannot delete this group it still contains subgroups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:213
+#: rhodecode/controllers/admin/repos_groups.py:218
+#, python-format
+msgid "error occurred during deletion of repos group %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:238
+#, fuzzy
+msgid "An error occurred during deletion of group user"
+msgstr "在搜索操作中发生异常"
+
+#: rhodecode/controllers/admin/repos_groups.py:258
+msgid "An error occurred during deletion of group users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:121
+#, python-format
+msgid "Repositories successfully rescanned added: %s,removed: %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:129
+msgid "Whoosh reindex task scheduled"
+msgstr "Whoosh 重新索引任务调度"
+
+#: rhodecode/controllers/admin/settings.py:160
+msgid "Updated application settings"
+msgstr "更新应用设置"
+
+#: rhodecode/controllers/admin/settings.py:164
+#: rhodecode/controllers/admin/settings.py:275
+msgid "error occurred during updating application settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:200
+#, fuzzy
+msgid "Updated visualisation settings"
+msgstr "更新应用设置"
+
+#: rhodecode/controllers/admin/settings.py:205
+#, fuzzy
+msgid "error occurred during updating visualisation settings"
+msgstr "提交时发生错误"
+
+#: rhodecode/controllers/admin/settings.py:271
+#, fuzzy
+msgid "Updated VCS settings"
+msgstr "更新 mercurial 设置"
+
+#: rhodecode/controllers/admin/settings.py:285
+msgid "Added new hook"
+msgstr "新增钩子"
+
+#: rhodecode/controllers/admin/settings.py:297
+msgid "Updated hooks"
+msgstr "更新钩子"
+
+#: rhodecode/controllers/admin/settings.py:301
+msgid "error occurred during hook creation"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:320
+msgid "Email task created"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:375
+msgid "You can't edit this user since it's crucial for entire application"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:406
+msgid "Your account was updated successfully"
+msgstr "你的帐号已经更新完成"
+
+#: rhodecode/controllers/admin/settings.py:421
+#: rhodecode/controllers/admin/users.py:191
+#, python-format
+msgid "error occurred during update of user %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:130
+#, python-format
+msgid "created user %s"
+msgstr "创建用户 %s"
+
+#: rhodecode/controllers/admin/users.py:142
+#, python-format
+msgid "error occurred during creation of user %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:171
+msgid "User updated successfully"
+msgstr "用户更新成功"
+
+#: rhodecode/controllers/admin/users.py:207
+msgid "successfully deleted user"
+msgstr "用户删除成功"
+
+#: rhodecode/controllers/admin/users.py:212
+msgid "An error occurred during deletion of user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:226
+msgid "You can't edit this user"
+msgstr "无法编辑该用户"
+
+#: rhodecode/controllers/admin/users.py:266
+msgid "Granted 'repository create' permission to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:271
+msgid "Revoked 'repository create' permission to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:277
+#, fuzzy
+msgid "Granted 'repository fork' permission to user"
+msgstr "版本库权限"
+
+#: rhodecode/controllers/admin/users.py:282
+#, fuzzy
+msgid "Revoked 'repository fork' permission to user"
+msgstr "版本库权限"
+
+#: rhodecode/controllers/admin/users.py:288
+#: rhodecode/controllers/admin/users_groups.py:255
+#, fuzzy
+msgid "An error occurred during permissions saving"
+msgstr "在搜索操作中发生异常"
+
+#: rhodecode/controllers/admin/users.py:303
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:309
+#, fuzzy
+msgid "An error occurred during email saving"
+msgstr "在搜索操作中发生异常"
+
+#: rhodecode/controllers/admin/users.py:319
+#, fuzzy
+msgid "Removed email from user"
+msgstr "移除版本库组 %s"
+
+#: rhodecode/controllers/admin/users_groups.py:84
+#, python-format
+msgid "created users group %s"
+msgstr "建立用户组 %s"
+
+#: rhodecode/controllers/admin/users_groups.py:95
+#, python-format
+msgid "error occurred during creation of users group %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:135
+#, python-format
+msgid "updated users group %s"
+msgstr "更新用户组 %s"
+
+#: rhodecode/controllers/admin/users_groups.py:157
+#, python-format
+msgid "error occurred during update of users group %s"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:174
+msgid "successfully deleted users group"
+msgstr "删除用户组成功"
+
+#: rhodecode/controllers/admin/users_groups.py:179
+msgid "An error occurred during deletion of users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:233
+msgid "Granted 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:238
+msgid "Revoked 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:244
+msgid "Granted 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:249
+msgid "Revoked 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/lib/auth.py:499
+msgid "You need to be a registered user to perform this action"
+msgstr "必须是注册用户才能进行此操作"
+
+#: rhodecode/lib/auth.py:540
+msgid "You need to be a signed in to view this page"
+msgstr "必须登录才能访问该页面"
+
+#: rhodecode/lib/diffs.py:86
+msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr "变更集因过大而被截断,可查看原始变更集作为替代"
+
+#: rhodecode/lib/diffs.py:96
+#, fuzzy
+msgid "No changes detected"
+msgstr "尚无修订"
+
+#: rhodecode/lib/helpers.py:372
+#, python-format
+msgid "%a, %d %b %Y %H:%M:%S"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:484
+msgid "True"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:488
+msgid "False"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:532
+#, fuzzy
+msgid "Changeset not found"
+msgstr "修改"
+
+#: rhodecode/lib/helpers.py:555
+#, python-format
+msgid "Show all combined changesets %s->%s"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:561
+msgid "compare view"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:581
+msgid "and"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:582
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
+msgid "revisions"
+msgstr "修订"
+
+#: rhodecode/lib/helpers.py:606
+msgid "fork name "
+msgstr "分支名称"
+
+#: rhodecode/lib/helpers.py:620
+#: rhodecode/templates/pullrequests/pullrequest_show.html:4
+#: rhodecode/templates/pullrequests/pullrequest_show.html:12
+#, python-format
+msgid "Pull request #%s"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:626
+msgid "[deleted] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
+msgid "[created] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:630
+#, fuzzy
+msgid "[created] repository as fork"
+msgstr "建立版本库 %s"
+
+#: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
+msgid "[forked] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
+msgid "[updated] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:636
+msgid "[delete] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:644
+#, fuzzy
+msgid "[created] user"
+msgstr "创建用户 %s"
+
+#: rhodecode/lib/helpers.py:646
+#, fuzzy
+msgid "[updated] user"
+msgstr "更新用户组 %s"
+
+#: rhodecode/lib/helpers.py:648
+#, fuzzy
+msgid "[created] users group"
+msgstr "建立用户组 %s"
+
+#: rhodecode/lib/helpers.py:650
+#, fuzzy
+msgid "[updated] users group"
+msgstr "更新用户组 %s"
+
+#: rhodecode/lib/helpers.py:652
+msgid "[commented] on revision in repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:654
+#, fuzzy
+msgid "[commented] on pull request for"
+msgstr "创建用户 %s"
+
+#: rhodecode/lib/helpers.py:656
+msgid "[closed] pull request for"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:658
+msgid "[pushed] into"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:660
+msgid "[committed via RhodeCode] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:662
+msgid "[pulled from remote] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:664
+msgid "[pulled] from"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:666
+msgid "[started following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:668
+msgid "[stopped following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:840
+#, python-format
+msgid " and %s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:844
+msgid "No Files"
+msgstr "没有文件"
+
+#: rhodecode/lib/utils2.py:335
+#, fuzzy, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "年"
+
+#: rhodecode/lib/utils2.py:336
+#, fuzzy, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "月"
+
+#: rhodecode/lib/utils2.py:337
+#, fuzzy, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "日"
+
+#: rhodecode/lib/utils2.py:338
+#, fuzzy, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "时"
+
+#: rhodecode/lib/utils2.py:339
+#, fuzzy, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "分"
+
+#: rhodecode/lib/utils2.py:340
+#, fuzzy, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] "秒"
+
+#: rhodecode/lib/utils2.py:355
+#, fuzzy, python-format
+msgid "%s ago"
+msgstr "之前"
+
+#: rhodecode/lib/utils2.py:357
+#, python-format
+msgid "%s and %s ago"
+msgstr ""
+
+#: rhodecode/lib/utils2.py:360
+msgid "just now"
+msgstr "现在"
+
+#: rhodecode/lib/celerylib/tasks.py:269
+#, fuzzy
+msgid "password reset link"
+msgstr "密码重置链接已经发送"
+
+#: rhodecode/model/comment.py:110
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: rhodecode/model/comment.py:157
+msgid "[Mention]"
+msgstr ""
+
+#: rhodecode/model/db.py:1140
+#, fuzzy
+msgid "Repository no access"
+msgstr "个版本库"
+
+#: rhodecode/model/db.py:1141
+#, fuzzy
+msgid "Repository read access"
+msgstr "这个版本库已经存在"
+
+#: rhodecode/model/db.py:1142
+#, fuzzy
+msgid "Repository write access"
+msgstr "个版本库"
+
+#: rhodecode/model/db.py:1143
+#, fuzzy
+msgid "Repository admin access"
+msgstr "个版本库"
+
+#: rhodecode/model/db.py:1145
+#, fuzzy
+msgid "Repositories Group no access"
+msgstr "版本库组"
+
+#: rhodecode/model/db.py:1146
+#, fuzzy
+msgid "Repositories Group read access"
+msgstr "版本库组"
+
+#: rhodecode/model/db.py:1147
+#, fuzzy
+msgid "Repositories Group write access"
+msgstr "版本库组"
+
+#: rhodecode/model/db.py:1148
+#, fuzzy
+msgid "Repositories Group admin access"
+msgstr "版本库组"
+
+#: rhodecode/model/db.py:1150
+#, fuzzy
+msgid "RhodeCode Administrator"
+msgstr "用户管理员"
+
+#: rhodecode/model/db.py:1151
+#, fuzzy
+msgid "Repository creation disabled"
+msgstr "建立版本库"
+
+#: rhodecode/model/db.py:1152
+#, fuzzy
+msgid "Repository creation enabled"
+msgstr "建立版本库"
+
+#: rhodecode/model/db.py:1153
+#, fuzzy
+msgid "Repository forking disabled"
+msgstr "建立版本库"
+
+#: rhodecode/model/db.py:1154
+#, fuzzy
+msgid "Repository forking enabled"
+msgstr "建立版本库"
+
+#: rhodecode/model/db.py:1155
+#, fuzzy
+msgid "Register disabled"
+msgstr "禁用"
+
+#: rhodecode/model/db.py:1156
+msgid "Register new user with RhodeCode with manual activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1159
+msgid "Register new user with RhodeCode with auto activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1579
+msgid "Not Reviewed"
+msgstr ""
+
+#: rhodecode/model/db.py:1580
+#, fuzzy
+msgid "Approved"
+msgstr "移除"
+
+#: rhodecode/model/db.py:1581
+msgid "Rejected"
+msgstr ""
+
+#: rhodecode/model/db.py:1582
+msgid "Under Review"
+msgstr ""
+
+#: rhodecode/model/forms.py:43
+msgid "Please enter a login"
+msgstr "请登录"
+
+#: rhodecode/model/forms.py:44
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr ""
+
+#: rhodecode/model/forms.py:52
+msgid "Please enter a password"
+msgstr "请输入密码"
+
+#: rhodecode/model/forms.py:53
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr ""
+
+#: rhodecode/model/notification.py:220
+msgid "commented on commit"
+msgstr ""
+
+#: rhodecode/model/notification.py:221
+#, fuzzy
+msgid "sent message"
+msgstr "提交信息"
+
+#: rhodecode/model/notification.py:222
+msgid "mentioned you"
+msgstr ""
+
+#: rhodecode/model/notification.py:223
+#, fuzzy
+msgid "registered in RhodeCode"
+msgstr "成功注册到 rhodecode"
+
+#: rhodecode/model/notification.py:224
+msgid "opened new pull request"
+msgstr ""
+
+#: rhodecode/model/notification.py:225
+msgid "commented on pull request"
+msgstr ""
+
+#: rhodecode/model/pull_request.py:84
+#, python-format
+msgid "%(user)s wants you to review pull request #%(pr_id)s"
+msgstr ""
+
+#: rhodecode/model/scm.py:535
+#, fuzzy
+msgid "latest tip"
+msgstr "最后登录"
+
+#: rhodecode/model/user.py:230
+#, fuzzy
+msgid "new user registration"
+msgstr "[RhodeCode] 新用户注册"
+
+#: rhodecode/model/user.py:255 rhodecode/model/user.py:277
+#: rhodecode/model/user.py:299
+msgid "You can't Edit this user since it's crucial for entire application"
+msgstr "由于是系统帐号,无法编辑该用户"
+
+#: rhodecode/model/user.py:323
+msgid "You can't remove this user since it's crucial for entire application"
+msgstr "由于是系统帐号,无法删除该用户"
+
+#: rhodecode/model/user.py:329
+#, fuzzy, python-format
+msgid ""
+"user \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories. %s"
+msgstr "由于该用户拥有版本库 %s 因而无法删除,请变更版本库所有者或删除版本库"
+
+#: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: rhodecode/model/validators.py:82
+#, fuzzy, python-format
+msgid "Username \"%(username)s\" already exists"
+msgstr "该用户名已经存在"
+
+#: rhodecode/model/validators.py:84
+#, python-format
+msgid "Username \"%(username)s\" is forbidden"
+msgstr ""
+
+#: rhodecode/model/validators.py:86
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or"
+" dashes and must begin with alphanumeric character"
+msgstr "只能使用字母、数字、下划线、小数点或减号作为用户名,且必须由数字或字母开头"
+
+#: rhodecode/model/validators.py:114
+#, fuzzy, python-format
+msgid "Username %(username)s is not valid"
+msgstr "用户或用户组名称无效"
+
+#: rhodecode/model/validators.py:133
+#, fuzzy
+msgid "Invalid users group name"
+msgstr "无效用户名"
+
+#: rhodecode/model/validators.py:134
+#, fuzzy, python-format
+msgid "Users group \"%(usersgroup)s\" already exists"
+msgstr "该用户组名称已经存在"
+
+#: rhodecode/model/validators.py:136
+msgid ""
+"users group name may only contain  alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
+msgstr "只能使用字母、数字、下划线、小数点或减号作为用户组名,且必须由数字或字母开头"
+
+#: rhodecode/model/validators.py:174
+msgid "Cannot assign this group as parent"
+msgstr ""
+
+#: rhodecode/model/validators.py:175
+#, fuzzy, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr "该用户名已经存在"
+
+#: rhodecode/model/validators.py:177
+#, fuzzy, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr "这个版本库已经存在"
+
+#: rhodecode/model/validators.py:235
+#, fuzzy
+msgid "Invalid characters (non-ascii) in password"
+msgstr "密码含有无效字符"
+
+#: rhodecode/model/validators.py:250
+msgid "Passwords do not match"
+msgstr "密码不符"
+
+#: rhodecode/model/validators.py:267
+msgid "invalid password"
+msgstr "无效密码"
+
+#: rhodecode/model/validators.py:268
+msgid "invalid user name"
+msgstr "无效用户名"
+
+#: rhodecode/model/validators.py:269
+msgid "Your account is disabled"
+msgstr "该帐号已被禁用"
+
+#: rhodecode/model/validators.py:313
+#, fuzzy, python-format
+msgid "Repository name %(repo)s is disallowed"
+msgstr "该版本库名称被禁用"
+
+#: rhodecode/model/validators.py:315
+#, fuzzy, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr "这个版本库已经存在"
+
+#: rhodecode/model/validators.py:316
+#, fuzzy, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
+msgstr "组中已经存在该版本库"
+
+#: rhodecode/model/validators.py:318
+#, fuzzy, python-format
+msgid "Repositories group with name \"%(repo)s\" already exists"
+msgstr "这个版本库已经存在"
+
+#: rhodecode/model/validators.py:431
+msgid "invalid clone url"
+msgstr "无效的 clone 地址"
+
+#: rhodecode/model/validators.py:432
+msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
+msgstr ""
+
+#: rhodecode/model/validators.py:457
+#, fuzzy
+msgid "Fork have to be the same type as parent"
+msgstr "分支必须使用相同的版本库类型"
+
+#: rhodecode/model/validators.py:478
+msgid "This username or users group name is not valid"
+msgstr "用户或用户组名称无效"
+
+#: rhodecode/model/validators.py:562
+msgid "This is not a valid path"
+msgstr "不是一个合法的路径"
+
+#: rhodecode/model/validators.py:577
+msgid "This e-mail address is already taken"
+msgstr "该邮件地址已被使用"
+
+#: rhodecode/model/validators.py:597
+#, fuzzy, python-format
+msgid "e-mail \"%(email)s\" does not exist."
+msgstr "该邮件地址不存在"
+
+#: rhodecode/model/validators.py:634
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: rhodecode/model/validators.py:653
+#, python-format
+msgid "Revisions %(revs)s are already part of pull request or have set status"
+msgstr ""
+
+#: rhodecode/templates/index.html:3
+msgid "Dashboard"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/repo_switcher_list.html:4
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/users/user_edit_my_account.html:31
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/bookmarks/bookmarks.html:10
+#: rhodecode/templates/branches/branches.html:9
+#: rhodecode/templates/journal/journal.html:40
+#: rhodecode/templates/tags/tags.html:10
+msgid "quick filter..."
+msgstr "快速过滤..."
+
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/base/base.html:221
+msgid "repositories"
+msgstr "个版本库"
+
+#: rhodecode/templates/index_base.html:13
+#: rhodecode/templates/index_base.html:15
+#: rhodecode/templates/admin/repos/repos.html:21
+msgid "ADD REPOSITORY"
+msgstr "新增版本库"
+
+#: rhodecode/templates/index_base.html:29
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
+#: rhodecode/templates/admin/users_groups/users_group_add.html:32
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:33
+msgid "Group name"
+msgstr "组名"
+
+#: rhodecode/templates/index_base.html:30
+#: rhodecode/templates/index_base.html:71
+#: rhodecode/templates/index_base.html:142
+#: rhodecode/templates/index_base.html:168
+#: rhodecode/templates/admin/repos/repo_add_base.html:56
+#: rhodecode/templates/admin/repos/repo_edit.html:75
+#: rhodecode/templates/admin/repos/repos.html:72
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
+#: rhodecode/templates/forks/fork.html:59
+#: rhodecode/templates/settings/repo_settings.html:66
+#: rhodecode/templates/summary/summary.html:105
+msgid "Description"
+msgstr "描述"
+
+#: rhodecode/templates/index_base.html:40
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
+msgid "Repositories group"
+msgstr "版本库组"
+
+#: rhodecode/templates/index_base.html:70
+#: rhodecode/templates/index_base.html:166
+#: rhodecode/templates/admin/repos/repo_add_base.html:9
+#: rhodecode/templates/admin/repos/repo_edit.html:32
+#: rhodecode/templates/admin/repos/repos.html:70
+#: rhodecode/templates/admin/users/user_edit.html:192
+#: rhodecode/templates/admin/users/user_edit_my_account.html:59
+#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
+#: rhodecode/templates/bookmarks/bookmarks.html:36
+#: rhodecode/templates/bookmarks/bookmarks_data.html:6
+#: rhodecode/templates/branches/branches.html:51
+#: rhodecode/templates/files/files_browser.html:47
+#: rhodecode/templates/journal/journal.html:59
+#: rhodecode/templates/journal/journal.html:107
+#: rhodecode/templates/journal/journal.html:186
+#: rhodecode/templates/settings/repo_settings.html:31
+#: rhodecode/templates/summary/summary.html:43
+#: rhodecode/templates/summary/summary.html:123
+#: rhodecode/templates/tags/tags.html:36
+#: rhodecode/templates/tags/tags_data.html:6
+msgid "Name"
+msgstr "名称"
+
+#: rhodecode/templates/index_base.html:72
+msgid "Last change"
+msgstr "最后修改"
+
+#: rhodecode/templates/index_base.html:73
+#: rhodecode/templates/index_base.html:171
+#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/journal/journal.html:188
+msgid "Tip"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:74
+#: rhodecode/templates/index_base.html:173
+#: rhodecode/templates/admin/repos/repo_edit.html:121
+#: rhodecode/templates/admin/repos/repos.html:73
+msgid "Owner"
+msgstr "所有者"
+
+#: rhodecode/templates/index_base.html:75
+#: rhodecode/templates/summary/summary.html:48
+#: rhodecode/templates/summary/summary.html:51
+msgid "RSS"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:76
+msgid "Atom"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:110
+#: rhodecode/templates/index_base.html:112
+#, python-format
+msgid "Subscribe to %s rss feed"
+msgstr "订阅 rss %s"
+
+#: rhodecode/templates/index_base.html:117
+#: rhodecode/templates/index_base.html:119
+#, python-format
+msgid "Subscribe to %s atom feed"
+msgstr "订阅 atom %s"
+
+#: rhodecode/templates/index_base.html:140
+#, fuzzy
+msgid "Group Name"
+msgstr "组名"
+
+#: rhodecode/templates/index_base.html:158
+#: rhodecode/templates/index_base.html:198
+#: rhodecode/templates/admin/repos/repos.html:94
+#: rhodecode/templates/admin/users/user_edit_my_account.html:179
+#: rhodecode/templates/admin/users/users.html:107
+#: rhodecode/templates/bookmarks/bookmarks.html:60
+#: rhodecode/templates/branches/branches.html:77
+#: rhodecode/templates/journal/journal.html:211
+#: rhodecode/templates/tags/tags.html:60
+msgid "Click to sort ascending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:159
+#: rhodecode/templates/index_base.html:199
+#: rhodecode/templates/admin/repos/repos.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account.html:180
+#: rhodecode/templates/admin/users/users.html:108
+#: rhodecode/templates/bookmarks/bookmarks.html:61
+#: rhodecode/templates/branches/branches.html:78
+#: rhodecode/templates/journal/journal.html:212
+#: rhodecode/templates/tags/tags.html:61
+msgid "Click to sort descending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:169
+#, fuzzy
+msgid "Last Change"
+msgstr "最后修改"
+
+#: rhodecode/templates/index_base.html:200
+#: rhodecode/templates/admin/repos/repos.html:96
+#: rhodecode/templates/admin/users/user_edit_my_account.html:181
+#: rhodecode/templates/admin/users/users.html:109
+#: rhodecode/templates/bookmarks/bookmarks.html:62
+#: rhodecode/templates/branches/branches.html:79
+#: rhodecode/templates/journal/journal.html:213
+#: rhodecode/templates/tags/tags.html:62
+msgid "No records found."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:201
+#: rhodecode/templates/admin/repos/repos.html:97
+#: rhodecode/templates/admin/users/user_edit_my_account.html:182
+#: rhodecode/templates/admin/users/users.html:110
+#: rhodecode/templates/bookmarks/bookmarks.html:63
+#: rhodecode/templates/branches/branches.html:80
+#: rhodecode/templates/journal/journal.html:214
+#: rhodecode/templates/tags/tags.html:63
+msgid "Data error."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:202
+#: rhodecode/templates/admin/repos/repos.html:98
+#: rhodecode/templates/admin/users/user_edit_my_account.html:183
+#: rhodecode/templates/admin/users/users.html:111
+#: rhodecode/templates/bookmarks/bookmarks.html:64
+#: rhodecode/templates/branches/branches.html:81
+#: rhodecode/templates/journal/journal.html:215
+#: rhodecode/templates/tags/tags.html:64
+#, fuzzy
+msgid "Loading..."
+msgstr "加载文件列表..."
+
+#: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
+msgid "Sign In"
+msgstr "登录"
+
+#: rhodecode/templates/login.html:21
+msgid "Sign In to"
+msgstr "登录到"
+
+#: 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
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
+#: rhodecode/templates/base/base.html:83
+#: rhodecode/templates/summary/summary.html:122
+msgid "Username"
+msgstr "帐号"
+
+#: 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
+msgid "Password"
+msgstr "密码"
+
+#: rhodecode/templates/login.html:50
+#, fuzzy
+msgid "Remember me"
+msgstr "成员"
+
+#: rhodecode/templates/login.html:60
+msgid "Forgot your password ?"
+msgstr "忘记了密码?"
+
+#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
+msgid "Don't have an account ?"
+msgstr "还没有帐号?"
+
+#: rhodecode/templates/password_reset.html:5
+msgid "Reset your password"
+msgstr "重置密码"
+
+#: rhodecode/templates/password_reset.html:11
+msgid "Reset your password to"
+msgstr "重置密码"
+
+#: rhodecode/templates/password_reset.html:21
+msgid "Email address"
+msgstr "邮件地址"
+
+#: rhodecode/templates/password_reset.html:30
+msgid "Reset my password"
+msgstr "重置密码"
+
+#: rhodecode/templates/password_reset.html:31
+msgid "Password reset link will be send to matching email address"
+msgstr "密码重置地址已经发送到邮件"
+
+#: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
+msgid "Sign Up"
+msgstr "注册"
+
+#: rhodecode/templates/register.html:11
+msgid "Sign Up to"
+msgstr "注册"
+
+#: rhodecode/templates/register.html:38
+msgid "Re-enter password"
+msgstr "确认密码"
+
+#: rhodecode/templates/register.html:47
+#: rhodecode/templates/admin/users/user_add.html:59
+#: rhodecode/templates/admin/users/user_edit.html:86
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
+msgid "First Name"
+msgstr "名"
+
+#: rhodecode/templates/register.html:56
+#: rhodecode/templates/admin/users/user_add.html:68
+#: rhodecode/templates/admin/users/user_edit.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
+msgid "Last Name"
+msgstr "姓"
+
+#: rhodecode/templates/register.html:65
+#: rhodecode/templates/admin/users/user_add.html:77
+#: rhodecode/templates/admin/users/user_edit.html:104
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
+#: rhodecode/templates/summary/summary.html:124
+msgid "Email"
+msgstr "电子邮件"
+
+#: rhodecode/templates/register.html:76
+msgid "Your account will be activated right after registration"
+msgstr "注册后,帐号将启用"
+
+#: rhodecode/templates/register.html:78
+msgid "Your account must wait for activation by administrator"
+msgstr "管理员审核后,你注册的帐号将被启用"
+
+#: rhodecode/templates/repo_switcher_list.html:11
+#: rhodecode/templates/admin/repos/repo_add_base.html:65
+#: rhodecode/templates/admin/repos/repo_edit.html:85
+#: rhodecode/templates/settings/repo_settings.html:76
+msgid "Private repository"
+msgstr "私有版本库"
+
+#: rhodecode/templates/repo_switcher_list.html:16
+msgid "Public repository"
+msgstr "公共版本库"
+
+#: rhodecode/templates/switch_to_list.html:3
+#: rhodecode/templates/branches/branches.html:14
+msgid "branches"
+msgstr "分支"
+
+#: rhodecode/templates/switch_to_list.html:10
+#: rhodecode/templates/branches/branches_data.html:57
+msgid "There are no branches yet"
+msgstr "没有任何分支"
+
+#: rhodecode/templates/switch_to_list.html:15
+#: rhodecode/templates/shortlog/shortlog_data.html:10
+#: rhodecode/templates/tags/tags.html:15
+msgid "tags"
+msgstr "标签"
+
+#: rhodecode/templates/switch_to_list.html:22
+#: rhodecode/templates/tags/tags_data.html:33
+msgid "There are no tags yet"
+msgstr "没有任何标签"
+
+#: rhodecode/templates/switch_to_list.html:28
+#: rhodecode/templates/bookmarks/bookmarks.html:15
+msgid "bookmarks"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:35
+#: rhodecode/templates/bookmarks/bookmarks_data.html:32
+#, fuzzy
+msgid "There are no bookmarks yet"
+msgstr "尚未有任何分支"
+
+#: rhodecode/templates/admin/admin.html:5
+#: rhodecode/templates/admin/admin.html:9
+msgid "Admin journal"
+msgstr "管理员日志"
+
+#: rhodecode/templates/admin/admin_log.html:6
+#: rhodecode/templates/admin/repos/repos.html:74
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
+#: rhodecode/templates/journal/journal.html:61
+#: rhodecode/templates/journal/journal.html:62
+msgid "Action"
+msgstr "操作"
+
+#: rhodecode/templates/admin/admin_log.html:7
+msgid "Repository"
+msgstr "版本库"
+
+#: rhodecode/templates/admin/admin_log.html:8
+#: rhodecode/templates/bookmarks/bookmarks.html:37
+#: rhodecode/templates/bookmarks/bookmarks_data.html:7
+#: rhodecode/templates/branches/branches.html:52
+#: rhodecode/templates/tags/tags.html:37
+#: rhodecode/templates/tags/tags_data.html:7
+msgid "Date"
+msgstr "日期"
+
+#: rhodecode/templates/admin/admin_log.html:9
+msgid "From IP"
+msgstr "来源 IP"
+
+#: rhodecode/templates/admin/admin_log.html:53
+msgid "No actions yet"
+msgstr "尚无操作"
+
+#: rhodecode/templates/admin/ldap/ldap.html:5
+msgid "LDAP administration"
+msgstr "LDAP 管理员"
+
+#: rhodecode/templates/admin/ldap/ldap.html:11
+msgid "Ldap"
+msgstr ""
+
+#: rhodecode/templates/admin/ldap/ldap.html:28
+msgid "Connection settings"
+msgstr "连接设置"
+
+#: rhodecode/templates/admin/ldap/ldap.html:30
+msgid "Enable LDAP"
+msgstr "启用 LDAP"
+
+#: rhodecode/templates/admin/ldap/ldap.html:34
+msgid "Host"
+msgstr "主机"
+
+#: rhodecode/templates/admin/ldap/ldap.html:38
+msgid "Port"
+msgstr "端口"
+
+#: rhodecode/templates/admin/ldap/ldap.html:42
+msgid "Account"
+msgstr "帐号"
+
+#: rhodecode/templates/admin/ldap/ldap.html:50
+msgid "Connection security"
+msgstr "连接安全"
+
+#: rhodecode/templates/admin/ldap/ldap.html:54
+msgid "Certificate Checks"
+msgstr "凭证确认"
+
+#: rhodecode/templates/admin/ldap/ldap.html:57
+msgid "Search settings"
+msgstr "搜索设置"
+
+#: rhodecode/templates/admin/ldap/ldap.html:59
+msgid "Base DN"
+msgstr ""
+
+#: rhodecode/templates/admin/ldap/ldap.html:63
+msgid "LDAP Filter"
+msgstr ""
+
+#: rhodecode/templates/admin/ldap/ldap.html:67
+msgid "LDAP Search Scope"
+msgstr ""
+
+#: rhodecode/templates/admin/ldap/ldap.html:70
+msgid "Attribute mappings"
+msgstr "属性映射"
+
+#: rhodecode/templates/admin/ldap/ldap.html:72
+msgid "Login Attribute"
+msgstr "登录属性"
+
+#: rhodecode/templates/admin/ldap/ldap.html:76
+msgid "First Name Attribute"
+msgstr "名"
+
+#: rhodecode/templates/admin/ldap/ldap.html:80
+msgid "Last Name Attribute"
+msgstr "姓"
+
+#: rhodecode/templates/admin/ldap/ldap.html:84
+msgid "E-mail Attribute"
+msgstr "电子邮件属性"
+
+#: rhodecode/templates/admin/ldap/ldap.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:141
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
+#: rhodecode/templates/admin/settings/hooks.html:73
+#: rhodecode/templates/admin/users/user_edit.html:129
+#: rhodecode/templates/admin/users/user_edit.html:174
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:135
+#: rhodecode/templates/settings/repo_settings.html:93
+msgid "Save"
+msgstr "保存"
+
+#: rhodecode/templates/admin/notifications/notifications.html:5
+#: rhodecode/templates/admin/notifications/notifications.html:9
+msgid "My Notifications"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:29
+msgid "All"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:30
+#, fuzzy
+msgid "Comments"
+msgstr "提交"
+
+#: rhodecode/templates/admin/notifications/notifications.html:31
+#: rhodecode/templates/base/base.html:254
+#: rhodecode/templates/base/base.html:256
+msgid "Pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:35
+msgid "Mark all read"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications_data.html:39
+#, fuzzy
+msgid "No notifications here yet"
+msgstr "尚无操作"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:5
+#: rhodecode/templates/admin/notifications/show_notification.html:11
+#, fuzzy
+msgid "Show notification"
+msgstr "显示注释"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:9
+#, fuzzy
+msgid "Notifications"
+msgstr "位置"
+
+#: rhodecode/templates/admin/permissions/permissions.html:5
+msgid "Permissions administration"
+msgstr "权限管理"
+
+#: rhodecode/templates/admin/permissions/permissions.html:11
+#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
+#: rhodecode/templates/admin/users/user_edit.html:139
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:100
+#: rhodecode/templates/settings/repo_settings.html:86
+msgid "Permissions"
+msgstr "权限"
+
+#: rhodecode/templates/admin/permissions/permissions.html:24
+msgid "Default permissions"
+msgstr "默认权限"
+
+#: rhodecode/templates/admin/permissions/permissions.html:31
+msgid "Anonymous access"
+msgstr "匿名访问"
+
+#: rhodecode/templates/admin/permissions/permissions.html:41
+msgid "Repository permission"
+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 ""
+
+#: rhodecode/templates/admin/permissions/permissions.html:50
+msgid "overwrite existing settings"
+msgstr "覆盖已有设置"
+
+#: rhodecode/templates/admin/permissions/permissions.html:55
+msgid "Registration"
+msgstr "注册"
+
+#: rhodecode/templates/admin/permissions/permissions.html:63
+msgid "Repository creation"
+msgstr "建立版本库"
+
+#: rhodecode/templates/admin/permissions/permissions.html:71
+#, fuzzy
+msgid "Repository forking"
+msgstr "建立版本库"
+
+#: rhodecode/templates/admin/permissions/permissions.html:78
+#: rhodecode/templates/admin/repos/repo_edit.html:241
+msgid "set"
+msgstr "设置"
+
+#: rhodecode/templates/admin/repos/repo_add.html:5
+#: rhodecode/templates/admin/repos/repo_add_create_repository.html:5
+msgid "Add repository"
+msgstr "添加版本库"
+
+#: rhodecode/templates/admin/repos/repo_add.html:11
+#: rhodecode/templates/admin/repos/repo_edit.html:11
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
+msgid "Repositories"
+msgstr "版本库"
+
+#: rhodecode/templates/admin/repos/repo_add.html:13
+msgid "add new"
+msgstr "新增"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:20
+#: rhodecode/templates/summary/summary.html:95
+#: rhodecode/templates/summary/summary.html:96
+msgid "Clone from"
+msgstr "clone 自"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:24
+#: rhodecode/templates/admin/repos/repo_edit.html:44
+#: rhodecode/templates/settings/repo_settings.html:43
+msgid "Optional http[s] url from which repository should be cloned."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:29
+#: rhodecode/templates/admin/repos/repo_edit.html:49
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:4
+#: rhodecode/templates/forks/fork.html:50
+#: rhodecode/templates/settings/repo_settings.html:48
+msgid "Repository group"
+msgstr "版本库组"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:33
+#: rhodecode/templates/forks/fork.html:54
+msgid "Optionaly select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:38
+#: rhodecode/templates/admin/repos/repo_edit.html:58
+msgid "Type"
+msgstr "类型"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:42
+#, fuzzy
+msgid "Type of repository to create."
+msgstr "建立版本库"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:47
+#: rhodecode/templates/admin/repos/repo_edit.html:66
+#: rhodecode/templates/forks/fork.html:41
+#: rhodecode/templates/settings/repo_settings.html:57
+#, fuzzy
+msgid "Landing revision"
+msgstr "下一个修订"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:51
+#: rhodecode/templates/admin/repos/repo_edit.html:70
+#: rhodecode/templates/forks/fork.html:45
+#: rhodecode/templates/settings/repo_settings.html:61
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:60
+#: 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."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:69
+#: 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 ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:73
+msgid "add"
+msgstr "新增"
+
+#: rhodecode/templates/admin/repos/repo_add_create_repository.html:9
+msgid "add new repository"
+msgstr "新增版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:5
+msgid "Edit repository"
+msgstr "编辑版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:13
+#: rhodecode/templates/admin/users/user_edit.html:13
+#: rhodecode/templates/admin/users/user_edit.html:224
+#: rhodecode/templates/admin/users/user_edit.html:226
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:13
+#: rhodecode/templates/files/files_source.html:44
+#: rhodecode/templates/journal/journal.html:81
+msgid "edit"
+msgstr "编辑"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:40
+#: rhodecode/templates/settings/repo_settings.html:39
+msgid "Clone uri"
+msgstr "clone 地址"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:53
+#: rhodecode/templates/settings/repo_settings.html:52
+msgid "Optional select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:94
+msgid "Enable statistics"
+msgstr "启用统计"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:98
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:103
+msgid "Enable downloads"
+msgstr "启用下载"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:107
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:112
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
+#, fuzzy
+msgid "Enable locking"
+msgstr "启用"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:116
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:126
+#, fuzzy
+msgid "Change owner of this repository."
+msgstr "%s 库的修改"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:142
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
+#: rhodecode/templates/admin/settings/settings.html:113
+#: rhodecode/templates/admin/settings/settings.html:168
+#: rhodecode/templates/admin/settings/settings.html:258
+#: rhodecode/templates/admin/users/user_edit.html:130
+#: rhodecode/templates/admin/users/user_edit.html:175
+#: rhodecode/templates/admin/users/user_edit.html:278
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:136
+#: rhodecode/templates/files/files_add.html:82
+#: rhodecode/templates/files/files_edit.html:68
+#: rhodecode/templates/pullrequests/pullrequest.html:124
+#: rhodecode/templates/settings/repo_settings.html:94
+msgid "Reset"
+msgstr "重置"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:152
+msgid "Administration"
+msgstr "管理"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:155
+msgid "Statistics"
+msgstr "统计"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:159
+msgid "Reset current statistics"
+msgstr "重置统计"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:159
+msgid "Confirm to remove current statistics"
+msgstr "确认移除当前统计"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:162
+msgid "Fetched to rev"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:163
+msgid "Stats gathered"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:171
+msgid "Remote"
+msgstr "远程"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Pull changes from remote location"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:175
+msgid "Confirm to pull changes from remote side"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:186
+msgid "Cache"
+msgstr "缓存"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:190
+msgid "Invalidate repository cache"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:190
+msgid "Confirm to invalidate repository cache"
+msgstr "确认清除版本库缓存"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:195
+#: rhodecode/templates/base/base.html:318
+#: rhodecode/templates/base/base.html:320
+#: rhodecode/templates/base/base.html:322
+msgid "Public journal"
+msgstr "公共日志"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:201
+msgid "Remove from public journal"
+msgstr "从公共日志删除"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:203
+msgid "Add to public journal"
+msgstr "添加到公共日志"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:208
+msgid ""
+"All actions made on this repository will be accessible to everyone in "
+"public journal"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:215
+#, fuzzy
+msgid "Locking"
+msgstr "解锁"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Unlock locked repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+#, fuzzy
+msgid "Confirm to unlock repository"
+msgstr "确认删除版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "lock repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+#, fuzzy
+msgid "Confirm to lock repository"
+msgstr "确认删除版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:224
+#, fuzzy
+msgid "Repository is not locked"
+msgstr "个版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:229
+msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:236
+msgid "Set as fork of"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:245
+msgid "Manually set this repository as a fork of another from the list"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:251
+#: rhodecode/templates/changeset/changeset_file_comment.html:26
+msgid "Delete"
+msgstr "删除"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+msgid "Remove this repository"
+msgstr "删除版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+#: rhodecode/templates/journal/journal.html:84
+msgid "Confirm to delete this repository"
+msgstr "确认删除版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:259
+msgid ""
+"This repository will be renamed in a special way in order to be "
+"unaccesible for RhodeCode and VCS systems.\n"
+"                         If you need fully delete it from filesystem "
+"please do it manually"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:3
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
+msgid "none"
+msgstr "无"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:4
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
+msgid "read"
+msgstr "读"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:5
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
+msgid "write"
+msgstr "写"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:6
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
+#: rhodecode/templates/admin/users/users.html:85
+#: rhodecode/templates/base/base.html:217
+msgid "admin"
+msgstr "管理员"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:7
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
+msgid "member"
+msgstr "成员"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
+#: rhodecode/templates/data_table/_dt_elements.html:67
+#: rhodecode/templates/journal/journal.html:132
+#: rhodecode/templates/summary/summary.html:76
+msgid "private repository"
+msgstr "私有版本库"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:19
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:28
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
+#, fuzzy
+msgid "default"
+msgstr "删除"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:33
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:58
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
+msgid "revoke"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:83
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
+msgid "Add another member"
+msgstr "添加成员"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:97
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
+msgid "Failed to remove user"
+msgstr "删除用户失败"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:112
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
+msgid "Failed to remove users group"
+msgstr "删除用户组失败"
+
+#: rhodecode/templates/admin/repos/repos.html:5
+msgid "Repositories administration"
+msgstr "版本库管理员"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:8
+msgid "Groups"
+msgstr "组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:12
+msgid "with"
+msgstr ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:5
+msgid "Add repos group"
+msgstr "添加版本库组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:10
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:10
+msgid "Repos groups"
+msgstr "版本库组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:12
+msgid "add new repos group"
+msgstr "添加新版本库组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:50
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:50
+msgid "Group parent"
+msgstr "上级组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
+#: rhodecode/templates/admin/users/user_add.html:94
+#: rhodecode/templates/admin/users_groups/users_group_add.html:49
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:90
+#: rhodecode/templates/pullrequests/pullrequest_show.html:113
+msgid "save"
+msgstr "保存"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:5
+msgid "Edit repos group"
+msgstr "编辑版本库组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:12
+msgid "edit repos group"
+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"
+msgstr ""
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
+msgid "Repositories groups administration"
+msgstr "版本库管理员"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:22
+msgid "ADD NEW GROUP"
+msgstr "添加组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
+#, fuzzy
+msgid "Number of toplevel repositories"
+msgstr "版本库数量"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
+#: rhodecode/templates/admin/users/users.html:87
+#: rhodecode/templates/admin/users_groups/users_groups.html:35
+msgid "action"
+msgstr "操作"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#: rhodecode/templates/admin/users/user_edit.html:255
+#: rhodecode/templates/admin/users_groups/users_groups.html:44
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#: rhodecode/templates/data_table/_dt_elements.html:103
+msgid "delete"
+msgstr "删除"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#, fuzzy, python-format
+msgid "Confirm to delete this group: %s"
+msgstr "确认删除该组"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
+msgid "There are no repositories groups yet"
+msgstr "没有版本库组"
+
+#: rhodecode/templates/admin/settings/hooks.html:5
+#: rhodecode/templates/admin/settings/settings.html:5
+msgid "Settings administration"
+msgstr "设置管理员"
+
+#: rhodecode/templates/admin/settings/hooks.html:9
+#: rhodecode/templates/admin/settings/settings.html:9
+#: rhodecode/templates/settings/repo_settings.html:13
+msgid "Settings"
+msgstr "设置"
+
+#: rhodecode/templates/admin/settings/hooks.html:24
+msgid "Built in hooks - read only"
+msgstr "内建钩子 - 只读"
+
+#: rhodecode/templates/admin/settings/hooks.html:40
+msgid "Custom hooks"
+msgstr "自定义钩子"
+
+#: rhodecode/templates/admin/settings/hooks.html:56
+msgid "remove"
+msgstr "删除"
+
+#: rhodecode/templates/admin/settings/hooks.html:88
+msgid "Failed to remove hook"
+msgstr "移除钩子失败"
+
+#: rhodecode/templates/admin/settings/settings.html:24
+msgid "Remap and rescan repositories"
+msgstr "重新扫描并映射版本库"
+
+#: rhodecode/templates/admin/settings/settings.html:32
+msgid "rescan option"
+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 "如果版本库已经从文件系统删除,但数据库仍然有遗留信息,请勾选该项进行清理"
+
+#: rhodecode/templates/admin/settings/settings.html:39
+msgid "destroy old data"
+msgstr "清理旧数据"
+
+#: rhodecode/templates/admin/settings/settings.html:41
+msgid ""
+"Rescan repositories location for new repositories. Also deletes obsolete "
+"if `destroy` flag is checked "
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:46
+msgid "Rescan repositories"
+msgstr "重新扫描版本库"
+
+#: rhodecode/templates/admin/settings/settings.html:52
+msgid "Whoosh indexing"
+msgstr "Whoosh 索引"
+
+#: rhodecode/templates/admin/settings/settings.html:60
+msgid "index build option"
+msgstr "构建索引选项"
+
+#: rhodecode/templates/admin/settings/settings.html:65
+msgid "build from scratch"
+msgstr "重新建立"
+
+#: rhodecode/templates/admin/settings/settings.html:71
+msgid "Reindex"
+msgstr "重新索引"
+
+#: rhodecode/templates/admin/settings/settings.html:77
+msgid "Global application settings"
+msgstr "全局设置"
+
+#: rhodecode/templates/admin/settings/settings.html:86
+msgid "Application name"
+msgstr "应用名称"
+
+#: rhodecode/templates/admin/settings/settings.html:95
+msgid "Realm text"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:104
+msgid "GA code"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:112
+#: rhodecode/templates/admin/settings/settings.html:167
+#: rhodecode/templates/admin/settings/settings.html:257
+msgid "Save settings"
+msgstr "保存设置"
+
+#: rhodecode/templates/admin/settings/settings.html:119
+#, fuzzy
+msgid "Visualisation settings"
+msgstr "全局设置"
+
+#: rhodecode/templates/admin/settings/settings.html:128
+#, fuzzy
+msgid "Icons"
+msgstr "选项"
+
+#: rhodecode/templates/admin/settings/settings.html:133
+msgid "Show public repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:137
+#, fuzzy
+msgid "Show private repo icon on repositories"
+msgstr "私有版本库"
+
+#: rhodecode/templates/admin/settings/settings.html:144
+#, fuzzy
+msgid "Meta-Tagging"
+msgstr "设置"
+
+#: rhodecode/templates/admin/settings/settings.html:149
+msgid "Stylify recognised metatags:"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:176
+#, fuzzy
+msgid "VCS settings"
+msgstr "设置"
+
+#: rhodecode/templates/admin/settings/settings.html:185
+msgid "Web"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:190
+#, fuzzy
+msgid "require ssl for vcs operations"
+msgstr "使用 SSL 推送"
+
+#: rhodecode/templates/admin/settings/settings.html:192
+msgid ""
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
+"will return HTTP Error 406: Not Acceptable"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:198
+msgid "Hooks"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:203
+msgid "Update repository after push (hg update)"
+msgstr "推送后更新版本库(hg update)"
+
+#: rhodecode/templates/admin/settings/settings.html:207
+msgid "Show repository size after push"
+msgstr "推送后显示版本库大小"
+
+#: rhodecode/templates/admin/settings/settings.html:211
+msgid "Log user push commands"
+msgstr "记录用户推送命令"
+
+#: rhodecode/templates/admin/settings/settings.html:215
+msgid "Log user pull commands"
+msgstr "记录用户拉取命令"
+
+#: rhodecode/templates/admin/settings/settings.html:219
+msgid "advanced setup"
+msgstr "高级设置"
+
+#: rhodecode/templates/admin/settings/settings.html:224
+#, fuzzy
+msgid "Mercurial Extensions"
+msgstr "Mercurial 版本库"
+
+#: rhodecode/templates/admin/settings/settings.html:229
+msgid "largefiles extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:233
+msgid "hgsubversion extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:235
+msgid ""
+"Requires hgsubversion library installed. Allows clonning from svn remote "
+"locations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:245
+msgid "Repositories location"
+msgstr "版本库路径"
+
+#: rhodecode/templates/admin/settings/settings.html:250
+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 "这是一个关键设置。如果确认修改该项设置,请重启服务以便设置生效。"
+
+#: rhodecode/templates/admin/settings/settings.html:251
+msgid "unlock"
+msgstr "解锁"
+
+#: rhodecode/templates/admin/settings/settings.html:252
+msgid ""
+"Location where repositories are stored. After changing this value a "
+"restart, and rescan is required"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:272
+msgid "Test Email"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:280
+#, fuzzy
+msgid "Email to"
+msgstr "电子邮件"
+
+#: rhodecode/templates/admin/settings/settings.html:288
+#, fuzzy
+msgid "Send"
+msgstr "秒"
+
+#: rhodecode/templates/admin/settings/settings.html:294
+msgid "System Info and Packages"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:297
+#, fuzzy
+msgid "show"
+msgstr "显示"
+
+#: rhodecode/templates/admin/users/user_add.html:5
+msgid "Add user"
+msgstr "添加用户"
+
+#: rhodecode/templates/admin/users/user_add.html:10
+#: rhodecode/templates/admin/users/user_edit.html:11
+msgid "Users"
+msgstr "用户"
+
+#: rhodecode/templates/admin/users/user_add.html:12
+msgid "add new user"
+msgstr "添加新用户"
+
+#: rhodecode/templates/admin/users/user_add.html:50
+#, fuzzy
+msgid "Password confirmation"
+msgstr "密码不符"
+
+#: rhodecode/templates/admin/users/user_add.html:86
+#: rhodecode/templates/admin/users/user_edit.html:113
+#: rhodecode/templates/admin/users_groups/users_group_add.html:41
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:42
+msgid "Active"
+msgstr "启用"
+
+#: rhodecode/templates/admin/users/user_edit.html:5
+msgid "Edit user"
+msgstr "编辑用户"
+
+#: rhodecode/templates/admin/users/user_edit.html:34
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
+msgid "Change your avatar at"
+msgstr "修改你的头像"
+
+#: rhodecode/templates/admin/users/user_edit.html:35
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
+msgid "Using"
+msgstr "使用中"
+
+#: rhodecode/templates/admin/users/user_edit.html:43
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
+msgid "API key"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:59
+msgid "LDAP DN"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:68
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
+msgid "New password"
+msgstr "新密码"
+
+#: rhodecode/templates/admin/users/user_edit.html:77
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
+msgid "New password confirmation"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:147
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:108
+#, fuzzy
+msgid "Inherit default permissions"
+msgstr "默认权限"
+
+#: rhodecode/templates/admin/users/user_edit.html:152
+#: 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 ""
+
+#: rhodecode/templates/admin/users/user_edit.html:158
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
+msgid "Create repositories"
+msgstr "创建版本库"
+
+#: rhodecode/templates/admin/users/user_edit.html:166
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:127
+#, fuzzy
+msgid "Fork repositories"
+msgstr "个版本库"
+
+#: rhodecode/templates/admin/users/user_edit.html:186
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
+#, fuzzy
+msgid "Nothing here yet"
+msgstr "尚无操作"
+
+#: rhodecode/templates/admin/users/user_edit.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account.html:60
+#: rhodecode/templates/admin/users/user_edit_my_account.html:194
+#, fuzzy
+msgid "Permission"
+msgstr "权限"
+
+#: rhodecode/templates/admin/users/user_edit.html:194
+#, fuzzy
+msgid "Edit Permission"
+msgstr "版本库权限"
+
+#: rhodecode/templates/admin/users/user_edit.html:243
+#, fuzzy
+msgid "Email addresses"
+msgstr "邮件地址"
+
+#: rhodecode/templates/admin/users/user_edit.html:256
+#, fuzzy, python-format
+msgid "Confirm to delete this email: %s"
+msgstr "确认删除该用户"
+
+#: rhodecode/templates/admin/users/user_edit.html:270
+#, fuzzy
+msgid "New email address"
+msgstr "邮件地址"
+
+#: rhodecode/templates/admin/users/user_edit.html:277
+#, fuzzy
+msgid "Add"
+msgstr "新增"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:5
+#: rhodecode/templates/base/base.html:124
+msgid "My account"
+msgstr "我的账户"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:9
+msgid "My Account"
+msgstr "我的账户"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:35
+#, fuzzy
+msgid "My permissions"
+msgstr "权限"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:38
+#: rhodecode/templates/journal/journal.html:41
+#, fuzzy
+msgid "My repos"
+msgstr "空版本库"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:41
+#, fuzzy
+msgid "My pull requests"
+msgstr "创建用户 %s"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:45
+#, fuzzy
+msgid "Add repo"
+msgstr "新增"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
+msgid "Opened by me"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
+#, python-format
+msgid "Pull request #%s opened on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
+#, fuzzy
+msgid "Confirm to delete this pull request"
+msgstr "确认删除版本库"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
+msgid "I participate in"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
+#, python-format
+msgid "Pull request #%s opened by %s on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
+#: rhodecode/templates/bookmarks/bookmarks.html:40
+#: rhodecode/templates/bookmarks/bookmarks_data.html:9
+#: rhodecode/templates/branches/branches.html:55
+#: rhodecode/templates/journal/journal.html:60
+#: rhodecode/templates/tags/tags.html:40
+#: rhodecode/templates/tags/tags_data.html:9
+msgid "Revision"
+msgstr "修订"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/journal/journal.html:81
+msgid "private"
+msgstr "私有"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#, fuzzy, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr "确认删除版本库"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
+#: rhodecode/templates/journal/journal.html:94
+msgid "No repositories yet"
+msgstr "没有任何版本库"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
+#: rhodecode/templates/journal/journal.html:96
+msgid "create one now"
+msgstr ""
+
+#: rhodecode/templates/admin/users/users.html:5
+msgid "Users administration"
+msgstr "用户管理员"
+
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/base/base.html:223
+msgid "users"
+msgstr "用户"
+
+#: rhodecode/templates/admin/users/users.html:23
+msgid "ADD NEW USER"
+msgstr "添加用户"
+
+#: rhodecode/templates/admin/users/users.html:77
+msgid "username"
+msgstr "用户名"
+
+#: rhodecode/templates/admin/users/users.html:80
+#, fuzzy
+msgid "firstname"
+msgstr "名"
+
+#: rhodecode/templates/admin/users/users.html:81
+msgid "lastname"
+msgstr "姓"
+
+#: rhodecode/templates/admin/users/users.html:82
+msgid "last login"
+msgstr "最后登录"
+
+#: rhodecode/templates/admin/users/users.html:84
+#: rhodecode/templates/admin/users_groups/users_groups.html:34
+msgid "active"
+msgstr "启用"
+
+#: rhodecode/templates/admin/users/users.html:86
+#: rhodecode/templates/base/base.html:226
+msgid "ldap"
+msgstr ""
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:5
+msgid "Add users group"
+msgstr "添加用户组"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:10
+#: rhodecode/templates/admin/users_groups/users_groups.html:9
+msgid "Users groups"
+msgstr "用户组"
+
+#: rhodecode/templates/admin/users_groups/users_group_add.html:12
+msgid "add new users group"
+msgstr "添加新用户组"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:5
+msgid "Edit users group"
+msgstr "编辑用户组"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:11
+msgid "UsersGroups"
+msgstr "用户组"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:50
+msgid "Members"
+msgstr "成员"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:58
+msgid "Choosen group members"
+msgstr "选择组成员"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:61
+msgid "Remove all elements"
+msgstr "移除全部项目"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:75
+msgid "Available members"
+msgstr "启用成员"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:79
+msgid "Add all elements"
+msgstr "添加全部项目"
+
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:146
+#, fuzzy
+msgid "Group members"
+msgstr "选择组成员"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:5
+msgid "Users groups administration"
+msgstr "用户组管理"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:23
+msgid "ADD NEW USER GROUP"
+msgstr "添加新用户组"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:32
+msgid "group name"
+msgstr "组名"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:33
+#: rhodecode/templates/base/root.html:46
+msgid "members"
+msgstr "成员"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:45
+#, fuzzy, python-format
+msgid "Confirm to delete this users group: %s"
+msgstr "确认删除该组"
+
+#: rhodecode/templates/base/base.html:41
+msgid "Submit a bug"
+msgstr "提交 bug"
+
+#: rhodecode/templates/base/base.html:77
+msgid "Login to your account"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:100
+msgid "Forgot password ?"
+msgstr "忘记密码?"
+
+#: rhodecode/templates/base/base.html:107
+#, fuzzy
+msgid "Log In"
+msgstr "登录"
+
+#: rhodecode/templates/base/base.html:118
+msgid "Inbox"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:122
+#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/base/base.html:302
+#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/bookmarks/bookmarks.html:11
+#: rhodecode/templates/branches/branches.html:10
+#: rhodecode/templates/changelog/changelog.html:10
+#: rhodecode/templates/changeset/changeset.html:10
+#: rhodecode/templates/changeset/changeset_range.html:9
+#: rhodecode/templates/compare/compare_diff.html:9
+#: rhodecode/templates/files/file_diff.html:8
+#: rhodecode/templates/files/files.html:8
+#: 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/pullrequests/pullrequest.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
+#: rhodecode/templates/settings/repo_settings.html:9
+#: rhodecode/templates/shortlog/shortlog.html:10
+#: rhodecode/templates/summary/summary.html:8
+#: rhodecode/templates/tags/tags.html:11
+msgid "Home"
+msgstr "首页"
+
+#: rhodecode/templates/base/base.html:123
+#: rhodecode/templates/base/base.html:309
+#: rhodecode/templates/base/base.html:311
+#: rhodecode/templates/base/base.html:313
+#: rhodecode/templates/journal/journal.html:4
+#: rhodecode/templates/journal/journal.html:21
+#: rhodecode/templates/journal/public_journal.html:4
+msgid "Journal"
+msgstr "日志"
+
+#: rhodecode/templates/base/base.html:125
+msgid "Log Out"
+msgstr "退出"
+
+#: rhodecode/templates/base/base.html:144
+msgid "Switch repository"
+msgstr "切换版本库"
+
+#: rhodecode/templates/base/base.html:146
+msgid "Products"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:152
+#: rhodecode/templates/base/base.html:182
+msgid "loading..."
+msgstr ""
+
+#: rhodecode/templates/base/base.html:158
+#: rhodecode/templates/base/base.html:160
+#: rhodecode/templates/base/base.html:162
+#: rhodecode/templates/data_table/_dt_elements.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:17
+#: rhodecode/templates/data_table/_dt_elements.html:19
+msgid "Summary"
+msgstr "概况"
+
+#: rhodecode/templates/base/base.html:166
+#: rhodecode/templates/base/base.html:168
+#: rhodecode/templates/base/base.html:170
+#: rhodecode/templates/changelog/changelog.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:23
+#: rhodecode/templates/data_table/_dt_elements.html:25
+#: rhodecode/templates/data_table/_dt_elements.html:27
+msgid "Changelog"
+msgstr "修改记录"
+
+#: rhodecode/templates/base/base.html:175
+#: rhodecode/templates/base/base.html:177
+#: rhodecode/templates/base/base.html:179
+msgid "Switch to"
+msgstr "切换到"
+
+#: rhodecode/templates/base/base.html:186
+#: rhodecode/templates/base/base.html:188
+#: rhodecode/templates/base/base.html:190
+#: rhodecode/templates/data_table/_dt_elements.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:33
+#: rhodecode/templates/data_table/_dt_elements.html:35
+msgid "Files"
+msgstr "档案"
+
+#: rhodecode/templates/base/base.html:195
+#: rhodecode/templates/base/base.html:199
+msgid "Options"
+msgstr "选项"
+
+#: rhodecode/templates/base/base.html:204
+#: rhodecode/templates/base/base.html:206
+#: rhodecode/templates/base/base.html:227
+msgid "settings"
+msgstr "设置"
+
+#: rhodecode/templates/base/base.html:209
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/forks/fork.html:13
+msgid "fork"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:211
+#: rhodecode/templates/changelog/changelog.html:40
+msgid "Open new pull request"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:213
+msgid "search"
+msgstr "搜索"
+
+#: rhodecode/templates/base/base.html:222
+msgid "repositories groups"
+msgstr "版本库组"
+
+#: rhodecode/templates/base/base.html:224
+msgid "users groups"
+msgstr "用户组"
+
+#: rhodecode/templates/base/base.html:225
+msgid "permissions"
+msgstr "权限"
+
+#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:240
+msgid "Followers"
+msgstr "跟随者"
+
+#: rhodecode/templates/base/base.html:246
+#: rhodecode/templates/base/base.html:248
+msgid "Forks"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:327
+#: rhodecode/templates/base/base.html:329
+#: rhodecode/templates/base/base.html:331
+#: rhodecode/templates/search/search.html:52
+msgid "Search"
+msgstr "搜索"
+
+#: rhodecode/templates/base/root.html:42
+#, fuzzy
+msgid "add another comment"
+msgstr "添加成员"
+
+#: rhodecode/templates/base/root.html:43
+#: rhodecode/templates/journal/journal.html:120
+#: rhodecode/templates/summary/summary.html:57
+msgid "Stop following this repository"
+msgstr "停止跟随该版本库"
+
+#: rhodecode/templates/base/root.html:44
+#: rhodecode/templates/summary/summary.html:61
+msgid "Start following this repository"
+msgstr "开始跟随该版本库"
+
+#: rhodecode/templates/base/root.html:45
+msgid "Group"
+msgstr "组"
+
+#: rhodecode/templates/base/root.html:47
+msgid "search truncated"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:48
+msgid "no matching files"
+msgstr "没有符合的文件"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:5
+#, python-format
+msgid "%s Bookmarks"
+msgstr ""
+
+#: rhodecode/templates/bookmarks/bookmarks.html:39
+#: rhodecode/templates/bookmarks/bookmarks_data.html:8
+#: rhodecode/templates/branches/branches.html:54
+#: rhodecode/templates/tags/tags.html:39
+#: rhodecode/templates/tags/tags_data.html:8
+#, fuzzy
+msgid "Author"
+msgstr "作者"
+
+#: rhodecode/templates/branches/branches.html:5
+#, fuzzy, python-format
+msgid "%s Branches"
+msgstr "分支"
+
+#: rhodecode/templates/branches/branches.html:29
+#, fuzzy
+msgid "Compare branches"
+msgstr "分支"
+
+#: rhodecode/templates/branches/branches.html:57
+#: rhodecode/templates/compare/compare_diff.html:5
+#: rhodecode/templates/compare/compare_diff.html:13
+#, fuzzy
+msgid "Compare"
+msgstr "比较显示"
+
+#: rhodecode/templates/branches/branches_data.html:6
+msgid "name"
+msgstr "名称"
+
+#: rhodecode/templates/branches/branches_data.html:7
+msgid "date"
+msgstr "日期"
+
+#: rhodecode/templates/branches/branches_data.html:8
+#: rhodecode/templates/shortlog/shortlog_data.html:8
+msgid "author"
+msgstr "作者"
+
+#: rhodecode/templates/branches/branches_data.html:9
+#: rhodecode/templates/shortlog/shortlog_data.html:5
+msgid "revision"
+msgstr "修订"
+
+#: rhodecode/templates/branches/branches_data.html:10
+#, fuzzy
+msgid "compare"
+msgstr "比较显示"
+
+#: rhodecode/templates/changelog/changelog.html:6
+#, fuzzy, python-format
+msgid "%s Changelog"
+msgstr "修改记录"
+
+#: rhodecode/templates/changelog/changelog.html:15
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:19
+#, python-format
+msgid "compare fork with %s"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:21
+msgid "Compare fork"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:46
+msgid "Show"
+msgstr "显示"
+
+#: rhodecode/templates/changelog/changelog.html:72
+#: rhodecode/templates/summary/summary.html:364
+msgid "show more"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:76
+msgid "Affected number of files, click to show more details"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:89
+#: rhodecode/templates/changeset/changeset.html:38
+#: rhodecode/templates/changeset/changeset_file_comment.html:20
+#: rhodecode/templates/changeset/changeset_range.html:46
+#, fuzzy
+msgid "Changeset status"
+msgstr "变更集"
+
+#: rhodecode/templates/changelog/changelog.html:92
+msgid "Click to open associated pull request"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:102
+#: rhodecode/templates/changeset/changeset.html:78
+msgid "Parent"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:108
+#: rhodecode/templates/changeset/changeset.html:84
+msgid "No parents"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:113
+#: rhodecode/templates/changeset/changeset.html:88
+msgid "merge"
+msgstr "合并"
+
+#: rhodecode/templates/changelog/changelog.html:116
+#: rhodecode/templates/changeset/changeset.html:91
+#: rhodecode/templates/files/files.html:29
+#: rhodecode/templates/files/files_add.html:33
+#: rhodecode/templates/files/files_edit.html:33
+#: rhodecode/templates/shortlog/shortlog_data.html:9
+msgid "branch"
+msgstr "分支"
+
+#: rhodecode/templates/changelog/changelog.html:122
+msgid "bookmark"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:128
+#: rhodecode/templates/changeset/changeset.html:96
+msgid "tag"
+msgstr "标签"
+
+#: rhodecode/templates/changelog/changelog.html:164
+msgid "Show selected changes __S -> __E"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:255
+msgid "There are no changes yet"
+msgstr "没有任何变更"
+
+#: rhodecode/templates/changelog/changelog_details.html:4
+#: rhodecode/templates/changeset/changeset.html:66
+msgid "removed"
+msgstr "移除"
+
+#: rhodecode/templates/changelog/changelog_details.html:5
+#: rhodecode/templates/changeset/changeset.html:67
+msgid "changed"
+msgstr "修改"
+
+#: rhodecode/templates/changelog/changelog_details.html:6
+#: rhodecode/templates/changeset/changeset.html:68
+msgid "added"
+msgstr "添加"
+
+#: rhodecode/templates/changelog/changelog_details.html:8
+#: rhodecode/templates/changelog/changelog_details.html:9
+#: rhodecode/templates/changelog/changelog_details.html:10
+#: rhodecode/templates/changeset/changeset.html:70
+#: rhodecode/templates/changeset/changeset.html:71
+#: rhodecode/templates/changeset/changeset.html:72
+#, python-format
+msgid "affected %s files"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset.html:6
+#, fuzzy, python-format
+msgid "%s Changeset"
+msgstr "无变更"
+
+#: rhodecode/templates/changeset/changeset.html:14
+msgid "Changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset.html:43
+#: rhodecode/templates/changeset/diff_block.html:20
+msgid "raw diff"
+msgstr "原始 diff"
+
+#: rhodecode/templates/changeset/changeset.html:44
+#: rhodecode/templates/changeset/diff_block.html:21
+msgid "download diff"
+msgstr "下载 diff"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, fuzzy, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "提交"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, python-format
+msgid "(%d inline)"
+msgid_plural "(%d inline)"
+msgstr[0] ""
+
+#: rhodecode/templates/changeset/changeset.html:103
+#, python-format
+msgid "%s files affected with %s insertions and %s deletions:"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset.html:119
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:42
+msgid "Submitting..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:45
+msgid "Commenting on line {1}."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:46
+#: rhodecode/templates/changeset/changeset_file_comment.html:121
+#, python-format
+msgid "Comments parsed using %s syntax with %s support."
+msgstr ""
+
+#: 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 ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:59
+#: rhodecode/templates/changeset/changeset_file_comment.html:138
+#, fuzzy
+msgid "Comment"
+msgstr "提交"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:60
+#: rhodecode/templates/changeset/changeset_file_comment.html:71
+msgid "Hide"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+#, fuzzy
+msgid "You need to be logged in to comment."
+msgstr "必须登录才能访问该页面"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "Login now"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:118
+msgid "Leave a comment"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "Check this to change current status of code-review for this changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+#, fuzzy
+msgid "change status"
+msgstr "变更集"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:140
+msgid "Comment and close"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:5
+#, fuzzy, python-format
+msgid "%s Changesets"
+msgstr "变更集"
+
+#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/templates/compare/compare_diff.html:29
+msgid "Compare View"
+msgstr "比较显示"
+
+#: rhodecode/templates/changeset/changeset_range.html:54
+#: rhodecode/templates/compare/compare_diff.html:41
+#: rhodecode/templates/pullrequests/pullrequest_show.html:69
+msgid "Files affected"
+msgstr ""
+
+#: rhodecode/templates/changeset/diff_block.html:19
+msgid "diff"
+msgstr ""
+
+#: rhodecode/templates/changeset/diff_block.html:27
+#, fuzzy
+msgid "show inline comments"
+msgstr "文件内容"
+
+#: rhodecode/templates/compare/compare_cs.html:5
+#, fuzzy
+msgid "No changesets"
+msgstr "尚无修订"
+
+#: rhodecode/templates/compare/compare_diff.html:37
+#, fuzzy
+msgid "Outgoing changesets"
+msgstr "尚无修订"
+
+#: rhodecode/templates/data_table/_dt_elements.html:39
+#: rhodecode/templates/data_table/_dt_elements.html:41
+#: rhodecode/templates/data_table/_dt_elements.html:43
+msgid "Fork"
+msgstr "分支"
+
+#: rhodecode/templates/data_table/_dt_elements.html:60
+#: rhodecode/templates/journal/journal.html:126
+#: rhodecode/templates/summary/summary.html:68
+msgid "Mercurial repository"
+msgstr "Mercurial 版本库"
+
+#: rhodecode/templates/data_table/_dt_elements.html:62
+#: rhodecode/templates/journal/journal.html:128
+#: rhodecode/templates/summary/summary.html:71
+msgid "Git repository"
+msgstr "Git 版本库"
+
+#: rhodecode/templates/data_table/_dt_elements.html:69
+#: rhodecode/templates/journal/journal.html:134
+#: rhodecode/templates/summary/summary.html:78
+msgid "public repository"
+msgstr "公共版本库"
+
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/summary/summary.html:87
+#: rhodecode/templates/summary/summary.html:88
+msgid "Fork of"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:92
+msgid "No changesets yet"
+msgstr "尚无修订"
+
+#: rhodecode/templates/data_table/_dt_elements.html:104
+#, fuzzy, python-format
+msgid "Confirm to delete this user: %s"
+msgstr "确认删除该用户"
+
+#: rhodecode/templates/email_templates/main.html:8
+msgid "This is an notification from RhodeCode."
+msgstr ""
+
+#: rhodecode/templates/errors/error_document.html:46
+#, python-format
+msgid "You will be redirected to %s in %s seconds"
+msgstr ""
+
+#: rhodecode/templates/files/file_diff.html:4
+#, fuzzy, python-format
+msgid "%s File diff"
+msgstr "文件 diff"
+
+#: rhodecode/templates/files/file_diff.html:12
+msgid "File diff"
+msgstr "文件 diff"
+
+#: rhodecode/templates/files/files.html:4
+#: rhodecode/templates/files/files.html:72
+#, fuzzy, python-format
+msgid "%s files"
+msgstr "文件"
+
+#: rhodecode/templates/files/files.html:12
+#: rhodecode/templates/summary/summary.html:340
+msgid "files"
+msgstr "文件"
+
+#: rhodecode/templates/files/files_add.html:4
+#: rhodecode/templates/files/files_edit.html:4
+#, fuzzy, python-format
+msgid "%s Edit file"
+msgstr "编辑文件"
+
+#: rhodecode/templates/files/files_add.html:19
+#, fuzzy
+msgid "add file"
+msgstr "编辑文件"
+
+#: rhodecode/templates/files/files_add.html:40
+#, fuzzy
+msgid "Add new file"
+msgstr "添加新用户"
+
+#: rhodecode/templates/files/files_add.html:45
+#, fuzzy
+msgid "File Name"
+msgstr "文件名"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:58
+#, fuzzy
+msgid "or"
+msgstr "时"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:54
+#, fuzzy
+msgid "Upload file"
+msgstr "编辑文件"
+
+#: rhodecode/templates/files/files_add.html:58
+#, fuzzy
+msgid "Create new file"
+msgstr "创建用户 %s"
+
+#: rhodecode/templates/files/files_add.html:63
+#: rhodecode/templates/files/files_edit.html:39
+#: rhodecode/templates/files/files_ypjax.html:3
+msgid "Location"
+msgstr "位置"
+
+#: rhodecode/templates/files/files_add.html:67
+msgid "use / to separate directories"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:77
+#: rhodecode/templates/files/files_edit.html:63
+#: rhodecode/templates/shortlog/shortlog_data.html:6
+msgid "commit message"
+msgstr "提交信息"
+
+#: rhodecode/templates/files/files_add.html:81
+#: rhodecode/templates/files/files_edit.html:67
+msgid "Commit changes"
+msgstr "提交修改"
+
+#: rhodecode/templates/files/files_browser.html:13
+msgid "view"
+msgstr "显示"
+
+#: rhodecode/templates/files/files_browser.html:14
+msgid "previous revision"
+msgstr "上一个修订"
+
+#: rhodecode/templates/files/files_browser.html:16
+msgid "next revision"
+msgstr "下一个修订"
+
+#: rhodecode/templates/files/files_browser.html:23
+msgid "follow current branch"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:27
+msgid "search file list"
+msgstr "搜索文件列表"
+
+#: rhodecode/templates/files/files_browser.html:31
+#: rhodecode/templates/shortlog/shortlog_data.html:65
+#, fuzzy
+msgid "add new file"
+msgstr "添加新用户"
+
+#: rhodecode/templates/files/files_browser.html:35
+msgid "Loading file list..."
+msgstr "加载文件列表..."
+
+#: rhodecode/templates/files/files_browser.html:48
+msgid "Size"
+msgstr "大小"
+
+#: rhodecode/templates/files/files_browser.html:49
+msgid "Mimetype"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:50
+#, fuzzy
+msgid "Last Revision"
+msgstr "下一个修订"
+
+#: rhodecode/templates/files/files_browser.html:51
+msgid "Last modified"
+msgstr "最后修改"
+
+#: rhodecode/templates/files/files_browser.html:52
+msgid "Last commiter"
+msgstr "最后提交"
+
+#: rhodecode/templates/files/files_edit.html:19
+msgid "edit file"
+msgstr "编辑文件"
+
+#: rhodecode/templates/files/files_edit.html:49
+#: rhodecode/templates/files/files_source.html:38
+msgid "show annotation"
+msgstr "显示注释"
+
+#: rhodecode/templates/files/files_edit.html:50
+#: rhodecode/templates/files/files_source.html:40
+#: rhodecode/templates/files/files_source.html:68
+msgid "show as raw"
+msgstr "显示原始文件"
+
+#: rhodecode/templates/files/files_edit.html:51
+#: rhodecode/templates/files/files_source.html:41
+msgid "download as raw"
+msgstr "下载原始文件"
+
+#: rhodecode/templates/files/files_edit.html:54
+#, fuzzy
+msgid "source"
+msgstr "显示代码"
+
+#: rhodecode/templates/files/files_edit.html:59
+#, fuzzy
+msgid "Editing file"
+msgstr "编辑文件"
+
+#: rhodecode/templates/files/files_source.html:2
+msgid "History"
+msgstr "历史"
+
+#: rhodecode/templates/files/files_source.html:9
+#, fuzzy
+msgid "diff to revision"
+msgstr "下一个修订"
+
+#: rhodecode/templates/files/files_source.html:10
+#, fuzzy
+msgid "show at revision"
+msgstr "下一个修订"
+
+#: rhodecode/templates/files/files_source.html:14
+#, fuzzy, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] "作者"
+
+#: rhodecode/templates/files/files_source.html:36
+msgid "show source"
+msgstr "显示代码"
+
+#: rhodecode/templates/files/files_source.html:59
+#, python-format
+msgid "Binary file (%s)"
+msgstr "二进制文件(%s)"
+
+#: rhodecode/templates/files/files_source.html:68
+msgid "File is too big to display"
+msgstr "文件过大,不能显示"
+
+#: rhodecode/templates/files/files_source.html:124
+msgid "Selection link"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:5
+#, fuzzy
+msgid "annotation"
+msgstr "显示注释"
+
+#: rhodecode/templates/files/files_ypjax.html:15
+msgid "Go back"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:16
+msgid "No files at given path"
+msgstr ""
+
+#: rhodecode/templates/followers/followers.html:5
+#, fuzzy, python-format
+msgid "%s Followers"
+msgstr "跟随者"
+
+#: rhodecode/templates/followers/followers.html:13
+msgid "followers"
+msgstr "跟随者"
+
+#: rhodecode/templates/followers/followers_data.html:12
+#, fuzzy
+msgid "Started following -"
+msgstr "开始跟随"
+
+#: rhodecode/templates/forks/fork.html:5
+#, fuzzy, python-format
+msgid "%s Fork"
+msgstr "分支"
+
+#: rhodecode/templates/forks/fork.html:31
+msgid "Fork name"
+msgstr "分支名"
+
+#: rhodecode/templates/forks/fork.html:68
+msgid "Private"
+msgstr "私有"
+
+#: rhodecode/templates/forks/fork.html:77
+#, fuzzy
+msgid "Copy permissions"
+msgstr "权限"
+
+#: rhodecode/templates/forks/fork.html:81
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:86
+msgid "Update after clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:90
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:94
+msgid "fork this repository"
+msgstr "对该版本库建立分支"
+
+#: rhodecode/templates/forks/forks.html:5
+#, fuzzy, python-format
+msgid "%s Forks"
+msgstr "分支"
+
+#: rhodecode/templates/forks/forks.html:13
+msgid "forks"
+msgstr "分支"
+
+#: rhodecode/templates/forks/forks_data.html:17
+msgid "forked"
+msgstr "已有分支"
+
+#: rhodecode/templates/forks/forks_data.html:38
+msgid "There are no forks yet"
+msgstr "尚未有任何分支"
+
+#: rhodecode/templates/journal/journal.html:13
+#, fuzzy
+msgid "ATOM journal feed"
+msgstr "公共日志 %s %s 订阅"
+
+#: rhodecode/templates/journal/journal.html:14
+#, fuzzy
+msgid "RSS journal feed"
+msgstr "公共日志 %s %s 订阅"
+
+#: rhodecode/templates/journal/journal.html:24
+#: rhodecode/templates/pullrequests/pullrequest.html:27
+msgid "Refresh"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:27
+#: rhodecode/templates/journal/public_journal.html:24
+#, fuzzy
+msgid "RSS feed"
+msgstr "%s %s 订阅"
+
+#: rhodecode/templates/journal/journal.html:30
+#: rhodecode/templates/journal/public_journal.html:27
+msgid "ATOM feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:41
+#, fuzzy
+msgid "Watched"
+msgstr "缓存"
+
+#: rhodecode/templates/journal/journal.html:46
+#, fuzzy
+msgid "ADD"
+msgstr "新增"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "following user"
+msgstr "跟随中用户"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "user"
+msgstr "用户"
+
+#: rhodecode/templates/journal/journal.html:147
+msgid "You are not following any users or repositories"
+msgstr "尚未跟随任何用户或版本库"
+
+#: rhodecode/templates/journal/journal_data.html:47
+msgid "No entries yet"
+msgstr ""
+
+#: rhodecode/templates/journal/public_journal.html:13
+#, fuzzy
+msgid "ATOM public journal feed"
+msgstr "公共日志 %s %s 订阅"
+
+#: rhodecode/templates/journal/public_journal.html:14
+#, fuzzy
+msgid "RSS public journal feed"
+msgstr "公共日志 %s %s 订阅"
+
+#: rhodecode/templates/journal/public_journal.html:21
+msgid "Public Journal"
+msgstr "公共日志"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:4
+#: rhodecode/templates/pullrequests/pullrequest.html:12
+msgid "New pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:28
+msgid "refresh overview"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:66
+#, fuzzy
+msgid "Detailed compare view"
+msgstr "比较显示"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:70
+#: rhodecode/templates/pullrequests/pullrequest_show.html:82
+msgid "Pull request reviewers"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:79
+#: rhodecode/templates/pullrequests/pullrequest_show.html:94
+#, fuzzy
+msgid "owner"
+msgstr "所有者"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:91
+#: rhodecode/templates/pullrequests/pullrequest_show.html:109
+msgid "Add reviewer to this pull request."
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:97
+#, fuzzy
+msgid "Create new pull request"
+msgstr "创建用户 %s"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:106
+#: rhodecode/templates/pullrequests/pullrequest_show.html:25
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
+#, fuzzy
+msgid "Title"
+msgstr "写"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:115
+#, fuzzy
+msgid "description"
+msgstr "描述"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:123
+msgid "Send pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:23
+#, python-format
+msgid "Closed %s"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:31
+#, fuzzy
+msgid "Status"
+msgstr "变更集"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:36
+msgid "Pull request status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:44
+msgid "Still not reviewed by"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:47
+#, python-format
+msgid "%d reviewer"
+msgid_plural "%d reviewers"
+msgstr[0] ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:54
+#, fuzzy
+msgid "Created on"
+msgstr "创建用户 %s"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:61
+#, fuzzy
+msgid "Compare view"
+msgstr "比较显示"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:65
+#, fuzzy
+msgid "Incoming changesets"
+msgstr "尚无修订"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
+#, fuzzy
+msgid "all pull requests"
+msgstr "创建用户 %s"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
+msgid "All pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
+msgid "Closed"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:6
+#, fuzzy, python-format
+msgid "Search \"%s\" in repository: %s"
+msgstr "在版本库:"
+
+#: rhodecode/templates/search/search.html:8
+#, fuzzy, python-format
+msgid "Search \"%s\" in all repositories"
+msgstr "在所有的版本库"
+
+#: rhodecode/templates/search/search.html:12
+#: rhodecode/templates/search/search.html:32
+#, fuzzy, python-format
+msgid "Search in repository: %s"
+msgstr "在版本库:"
+
+#: rhodecode/templates/search/search.html:14
+#: rhodecode/templates/search/search.html:34
+#, fuzzy
+msgid "Search in all repositories"
+msgstr "在所有的版本库"
+
+#: rhodecode/templates/search/search.html:48
+msgid "Search term"
+msgstr "搜索短语"
+
+#: rhodecode/templates/search/search.html:60
+msgid "Search in"
+msgstr "搜索范围"
+
+#: rhodecode/templates/search/search.html:63
+msgid "File contents"
+msgstr "文件内容"
+
+#: rhodecode/templates/search/search.html:64
+#, fuzzy
+msgid "Commit messages"
+msgstr "提交信息"
+
+#: rhodecode/templates/search/search.html:65
+msgid "File names"
+msgstr "文件名"
+
+#: rhodecode/templates/search/search_commit.html:35
+#: rhodecode/templates/search/search_content.html:21
+#: rhodecode/templates/search/search_path.html:15
+msgid "Permission denied"
+msgstr "权限不足"
+
+#: rhodecode/templates/settings/repo_settings.html:5
+#, fuzzy, python-format
+msgid "%s Settings"
+msgstr "设置"
+
+#: rhodecode/templates/shortlog/shortlog.html:5
+#, fuzzy, python-format
+msgid "%s Shortlog"
+msgstr "简短日志"
+
+#: rhodecode/templates/shortlog/shortlog.html:14
+msgid "shortlog"
+msgstr "简短日志"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:7
+msgid "age"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:18
+#, fuzzy
+msgid "No commit message"
+msgstr "提交信息"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:62
+msgid "Add or upload files directly via RhodeCode"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:71
+msgid "Push new repo"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:79
+#, fuzzy
+msgid "Existing repository?"
+msgstr "Git 版本库"
+
+#: rhodecode/templates/summary/summary.html:4
+#, fuzzy, python-format
+msgid "%s Summary"
+msgstr "概要"
+
+#: rhodecode/templates/summary/summary.html:12
+msgid "summary"
+msgstr "概要"
+
+#: rhodecode/templates/summary/summary.html:20
+#, fuzzy, python-format
+msgid "repo %s ATOM feed"
+msgstr "订阅 atom %s"
+
+#: rhodecode/templates/summary/summary.html:21
+#, fuzzy, python-format
+msgid "repo %s RSS feed"
+msgstr "订阅 rss %s"
+
+#: rhodecode/templates/summary/summary.html:49
+#: rhodecode/templates/summary/summary.html:52
+#, fuzzy
+msgid "ATOM"
+msgstr "作者"
+
+#: rhodecode/templates/summary/summary.html:82
+#, fuzzy, python-format
+msgid "Non changable ID %s"
+msgstr "无变更"
+
+#: rhodecode/templates/summary/summary.html:87
+msgid "public"
+msgstr "公共"
+
+#: rhodecode/templates/summary/summary.html:95
+msgid "remote clone"
+msgstr "远程 clone"
+
+#: rhodecode/templates/summary/summary.html:116
+msgid "Contact"
+msgstr "联系方式"
+
+#: rhodecode/templates/summary/summary.html:130
+msgid "Clone url"
+msgstr "clone 地址"
+
+#: rhodecode/templates/summary/summary.html:133
+msgid "Show by Name"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:134
+msgid "Show by ID"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:142
+#, fuzzy
+msgid "Trending files"
+msgstr "编辑文件"
+
+#: rhodecode/templates/summary/summary.html:150
+#: rhodecode/templates/summary/summary.html:166
+#: rhodecode/templates/summary/summary.html:194
+msgid "enable"
+msgstr "启用"
+
+#: rhodecode/templates/summary/summary.html:158
+msgid "Download"
+msgstr "下载"
+
+#: rhodecode/templates/summary/summary.html:162
+msgid "There are no downloads yet"
+msgstr "尚无任何下载"
+
+#: rhodecode/templates/summary/summary.html:164
+msgid "Downloads are disabled for this repository"
+msgstr "这个版本库的下载已经禁用"
+
+#: rhodecode/templates/summary/summary.html:170
+#, fuzzy
+msgid "Download as zip"
+msgstr "下载原始文件"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:186
+msgid "Commit activity by day / author"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:197
+msgid "Stats gathered: "
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:218
+msgid "Shortlog"
+msgstr "简短日志"
+
+#: rhodecode/templates/summary/summary.html:220
+#, fuzzy
+msgid "Quick start"
+msgstr "快速过滤..."
+
+#: rhodecode/templates/summary/summary.html:233
+#, python-format
+msgid "Readme file at revision '%s'"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:236
+msgid "Permalink to this readme"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:293
+#, python-format
+msgid "Download %s as %s"
+msgstr "下载 %s 作为 %s"
+
+#: rhodecode/templates/summary/summary.html:650
+msgid "commits"
+msgstr "提交"
+
+#: rhodecode/templates/summary/summary.html:651
+msgid "files added"
+msgstr "文件已添加"
+
+#: rhodecode/templates/summary/summary.html:652
+msgid "files changed"
+msgstr "文件已更改"
+
+#: rhodecode/templates/summary/summary.html:653
+msgid "files removed"
+msgstr "文件已删除"
+
+#: rhodecode/templates/summary/summary.html:656
+msgid "commit"
+msgstr "提交"
+
+#: rhodecode/templates/summary/summary.html:657
+msgid "file added"
+msgstr "文件已添加"
+
+#: rhodecode/templates/summary/summary.html:658
+msgid "file changed"
+msgstr "文件已更改"
+
+#: rhodecode/templates/summary/summary.html:659
+msgid "file removed"
+msgstr "文件已删除"
+
+#: rhodecode/templates/tags/tags.html:5
+#, fuzzy, python-format
+msgid "%s Tags"
+msgstr "之前"
+
diff --git a/rhodecode/i18n/zh_TW/LC_MESSAGES/rhodecode.po b/rhodecode/i18n/zh_TW/LC_MESSAGES/rhodecode.po
--- a/rhodecode/i18n/zh_TW/LC_MESSAGES/rhodecode.po
+++ b/rhodecode/i18n/zh_TW/LC_MESSAGES/rhodecode.po
@@ -1,4 +1,4 @@
-# Translations template for RhodeCode.
+# Chinese (Taiwan) translations for RhodeCode.
 # Copyright (C) 2011 ORGANIZATION
 # This file is distributed under the same license as the RhodeCode project.
 # FIRST AUTHOR , 2011.
@@ -7,33 +7,49 @@ msgid ""
 msgstr ""
 "Project-Id-Version: RhodeCode 1.2.0\n"
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2011-09-14 15:50-0300\n"
+"POT-Creation-Date: 2012-09-02 20:30+0200\n"
 "PO-Revision-Date: 2012-05-09 22:23+0800\n"
 "Last-Translator: Nansen \n"
-"Language-Team: LANGUAGE \n"
+"Language-Team: zh_TW \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-Poedit-Language: Chinese\n"
-"X-Poedit-Country: TAIWAN\n"
-"X-Poedit-SourceCharset: utf-8\n"
-
-#: rhodecode/controllers/changeset.py:108
-#: rhodecode/controllers/changeset.py:149
-#: rhodecode/controllers/changeset.py:216
-#: rhodecode/controllers/changeset.py:229
+
+#: rhodecode/controllers/changelog.py:94
+#, fuzzy
+msgid "All Branches"
+msgstr "分支"
+
+#: rhodecode/controllers/changeset.py:83
+msgid "show white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
+msgid "ignore white space"
+msgstr ""
+
+#: rhodecode/controllers/changeset.py:157
+#, fuzzy, python-format
+msgid "%s line context"
+msgstr "文件內容"
+
+#: rhodecode/controllers/changeset.py:333
+#: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
 msgid "binary file"
 msgstr "二進位檔"
 
-#: rhodecode/controllers/changeset.py:123
-#: rhodecode/controllers/changeset.py:168
-msgid "Changeset is to big and was cut off, see raw changeset instead"
-msgstr ""
-
-#: rhodecode/controllers/changeset.py:159
-msgid "Diff is to big and was cut off, see raw diff instead"
-msgstr ""
+#: rhodecode/controllers/changeset.py:408
+msgid ""
+"Changing status on a changeset associated witha closed pull request is "
+"not allowed"
+msgstr ""
+
+#: rhodecode/controllers/compare.py:69
+#, fuzzy
+msgid "There are no changesets yet"
+msgstr "尚未有任何變更"
 
 #: rhodecode/controllers/error.py:69
 msgid "Home page"
@@ -56,250 +72,313 @@ msgid "The resource could not be found"
 msgstr "找不到這個資源"
 
 #: rhodecode/controllers/error.py:107
-msgid "The server encountered an unexpected condition which prevented it from fulfilling the request."
-msgstr ""
-
-#: rhodecode/controllers/feed.py:48
+msgid ""
+"The server encountered an unexpected condition which prevented it from "
+"fulfilling the request."
+msgstr ""
+
+#: rhodecode/controllers/feed.py:49
 #, python-format
 msgid "Changes on %s repository"
 msgstr "修改於版本庫 %s"
 
-#: rhodecode/controllers/feed.py:49
+#: rhodecode/controllers/feed.py:50
 #, python-format
 msgid "%s %s feed"
 msgstr ""
 
-#: rhodecode/controllers/files.py:72
-msgid "There are no files yet"
+#: rhodecode/controllers/feed.py:75
+#, fuzzy
+msgid "commited on"
+msgstr "遞交"
+
+#: rhodecode/controllers/files.py:84
+#, fuzzy
+msgid "click here to add new file"
+msgstr "新增使用者"
+
+#: rhodecode/controllers/files.py:85
+#, fuzzy, python-format
+msgid "There are no files yet %s"
 msgstr "尚未有任何檔案"
 
-#: rhodecode/controllers/files.py:262
+#: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
+#, python-format
+msgid "This repository is has been locked by %s on %s"
+msgstr ""
+
+#: rhodecode/controllers/files.py:266
 #, python-format
 msgid "Edited %s via RhodeCode"
 msgstr "使用 RhodeCode 編輯 %s"
 
-#: rhodecode/controllers/files.py:267
-#: rhodecode/templates/files/file_diff.html:40
+#: rhodecode/controllers/files.py:271
 msgid "No changes"
 msgstr "沒有修改"
 
-#: rhodecode/controllers/files.py:278
+#: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
 #, python-format
 msgid "Successfully committed to %s"
 msgstr "成功遞交至 %s"
 
-#: rhodecode/controllers/files.py:283
+#: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
 msgid "Error occurred during commit"
 msgstr ""
 
-#: rhodecode/controllers/files.py:308
+#: rhodecode/controllers/files.py:318
+#, fuzzy, python-format
+msgid "Added %s via RhodeCode"
+msgstr "使用 RhodeCode 編輯 %s"
+
+#: rhodecode/controllers/files.py:332
+#, fuzzy
+msgid "No content"
+msgstr "文件內容"
+
+#: rhodecode/controllers/files.py:336
+#, fuzzy
+msgid "No filename"
+msgstr "檔案名稱"
+
+#: rhodecode/controllers/files.py:378
 msgid "downloads disabled"
 msgstr "下載已關閉"
 
-#: rhodecode/controllers/files.py:313
+#: rhodecode/controllers/files.py:389
 #, python-format
 msgid "Unknown revision %s"
 msgstr "未知修訂 %s"
 
-#: rhodecode/controllers/files.py:315
+#: rhodecode/controllers/files.py:391
 msgid "Empty repository"
 msgstr "空的版本庫"
 
-#: rhodecode/controllers/files.py:317
+#: rhodecode/controllers/files.py:393
 msgid "Unknown archive type"
 msgstr "未知的存檔類型"
 
-#: rhodecode/controllers/files.py:385
-#: rhodecode/controllers/files.py:398
-msgid "Binary file"
-msgstr "二進位檔"
-
-#: rhodecode/controllers/files.py:417
-#: rhodecode/templates/changeset/changeset_range.html:4
-#: rhodecode/templates/changeset/changeset_range.html:12
-#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/controllers/files.py:494
+#: rhodecode/templates/changeset/changeset_range.html:13
+#: rhodecode/templates/changeset/changeset_range.html:31
 msgid "Changesets"
 msgstr "變更"
 
-#: rhodecode/controllers/files.py:418
-#: rhodecode/controllers/summary.py:175
-#: rhodecode/templates/branches/branches.html:5
-#: rhodecode/templates/summary/summary.html:690
+#: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
+#: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
 msgid "Branches"
 msgstr "分支"
 
-#: rhodecode/controllers/files.py:419
-#: rhodecode/controllers/summary.py:176
-#: rhodecode/templates/summary/summary.html:679
-#: rhodecode/templates/tags/tags.html:5
+#: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
+#: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
 msgid "Tags"
 msgstr "標籤"
 
-#: rhodecode/controllers/journal.py:50
+#: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
+#, 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 ""
+
+#: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
+#, python-format
+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 ""
+
+#: rhodecode/controllers/forks.py:167
 #, python-format
-msgid "%s public journal %s feed"
-msgstr "%s 公開日誌 %s feed"
-
-#: rhodecode/controllers/journal.py:178
-#: rhodecode/controllers/journal.py:212
-#: rhodecode/templates/admin/repos/repo_edit.html:171
-#: rhodecode/templates/base/base.html:50
-msgid "Public journal"
+msgid "forked %s repository as %s"
+msgstr "forked %s 版本庫為 %s"
+
+#: rhodecode/controllers/forks.py:181
+#, python-format
+msgid "An error occurred during repository forking %s"
+msgstr ""
+
+#: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
+#, fuzzy
+msgid "public journal"
 msgstr "公開日誌"
 
-#: rhodecode/controllers/login.py:111
+#: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
+#: rhodecode/templates/base/base.html:220
+msgid "journal"
+msgstr "日誌"
+
+#: rhodecode/controllers/login.py:143
 msgid "You have successfully registered into rhodecode"
 msgstr "您已經成功註冊rhodecode"
 
-#: rhodecode/controllers/login.py:133
+#: rhodecode/controllers/login.py:164
 msgid "Your password reset link was sent"
 msgstr "您的密碼重設連結已寄出"
 
-#: rhodecode/controllers/login.py:155
-msgid "Your password reset was successful, new password has been sent to your email"
+#: rhodecode/controllers/login.py:184
+msgid ""
+"Your password reset was successful, new password has been sent to your "
+"email"
 msgstr "您的密碼重設動作已完成,新的密碼已寄至您的信箱"
 
-#: rhodecode/controllers/search.py:109
+#: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
+msgid "Bookmarks"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:158
+msgid "Pull request requires a title with min. 3 chars"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:160
+#, fuzzy
+msgid "error during creation of pull request"
+msgstr "建立使用者 %s"
+
+#: rhodecode/controllers/pullrequests.py:181
+#, fuzzy
+msgid "Successfully opened new pull request"
+msgstr "成功刪除使用者"
+
+#: rhodecode/controllers/pullrequests.py:184
+msgid "Error occurred during sending pull request"
+msgstr ""
+
+#: rhodecode/controllers/pullrequests.py:217
+#, fuzzy
+msgid "Successfully deleted pull request"
+msgstr "成功刪除使用者"
+
+#: rhodecode/controllers/search.py:131
 msgid "Invalid search query. Try quoting it."
 msgstr "無效的查詢。請使用跳脫字元"
 
-#: rhodecode/controllers/search.py:114
+#: rhodecode/controllers/search.py:136
 msgid "There is no index to search in. Please run whoosh indexer"
 msgstr "沒有任何索引可以搜尋。請執行 whoosh 建立索引"
 
-#: rhodecode/controllers/search.py:118
+#: rhodecode/controllers/search.py:140
 msgid "An error occurred during this search operation"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:61
-#: rhodecode/controllers/settings.py:171
-#, python-format
-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 ""
-
-#: rhodecode/controllers/settings.py:109
-#: rhodecode/controllers/admin/repos.py:239
+#: rhodecode/controllers/settings.py:107
+#: rhodecode/controllers/admin/repos.py:266
 #, python-format
 msgid "Repository %s updated successfully"
 msgstr "版本庫 %s 更新完成"
 
-#: rhodecode/controllers/settings.py:126
-#: rhodecode/controllers/admin/repos.py:257
+#: rhodecode/controllers/settings.py:125
+#: rhodecode/controllers/admin/repos.py:284
 #, python-format
 msgid "error occurred during update of repository %s"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:144
-#: rhodecode/controllers/admin/repos.py:275
+#: rhodecode/controllers/settings.py:143
+#: rhodecode/controllers/admin/repos.py:302
 #, python-format
-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 ""
-
-#: rhodecode/controllers/settings.py:156
-#: rhodecode/controllers/admin/repos.py:287
+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 ""
+
+#: rhodecode/controllers/settings.py:155
+#: rhodecode/controllers/admin/repos.py:314
 #, python-format
 msgid "deleted repository %s"
 msgstr "刪除版本庫 %s"
 
 #: rhodecode/controllers/settings.py:159
-#: rhodecode/controllers/admin/repos.py:297
-#: rhodecode/controllers/admin/repos.py:303
+#: rhodecode/controllers/admin/repos.py:324
+#: rhodecode/controllers/admin/repos.py:330
 #, python-format
 msgid "An error occurred during deletion of %s"
 msgstr ""
 
-#: rhodecode/controllers/settings.py:193
-#, python-format
-msgid "forked %s repository as %s"
-msgstr "forked %s 版本庫為 %s"
-
-#: rhodecode/controllers/settings.py:211
-#, python-format
-msgid "An error occurred during repository forking %s"
-msgstr ""
-
-#: rhodecode/controllers/summary.py:123
+#: rhodecode/controllers/summary.py:138
 msgid "No data loaded yet"
 msgstr ""
 
-#: rhodecode/controllers/summary.py:126
+#: rhodecode/controllers/summary.py:142
+#: rhodecode/templates/summary/summary.html:148
 msgid "Statistics are disabled for this repository"
 msgstr "這個版本庫的統計功能已停用"
 
-#: rhodecode/controllers/admin/ldap_settings.py:49
+#: rhodecode/controllers/admin/ldap_settings.py:50
 msgid "BASE"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:50
-msgid "ONELEVEL"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:51
+msgid "ONELEVEL"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:52
 msgid "SUBTREE"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:55
-msgid "NEVER"
-msgstr ""
-
 #: rhodecode/controllers/admin/ldap_settings.py:56
-msgid "ALLOW"
+msgid "NEVER"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:57
-msgid "TRY"
+msgid "ALLOW"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:58
-msgid "DEMAND"
+msgid "TRY"
 msgstr ""
 
 #: rhodecode/controllers/admin/ldap_settings.py:59
+msgid "DEMAND"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:60
 msgid "HARD"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:63
-msgid "No encryption"
-msgstr "無加密"
-
 #: rhodecode/controllers/admin/ldap_settings.py:64
-msgid "LDAPS connection"
-msgstr ""
+msgid "No encryption"
+msgstr "無加密"
 
 #: rhodecode/controllers/admin/ldap_settings.py:65
+msgid "LDAPS connection"
+msgstr ""
+
+#: rhodecode/controllers/admin/ldap_settings.py:66
 msgid "START_TLS on LDAP connection"
 msgstr ""
 
-#: rhodecode/controllers/admin/ldap_settings.py:115
+#: rhodecode/controllers/admin/ldap_settings.py:126
 msgid "Ldap settings updated successfully"
 msgstr "LDAP設定更新完成"
 
-#: rhodecode/controllers/admin/ldap_settings.py:120
+#: rhodecode/controllers/admin/ldap_settings.py:130
 msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
 msgstr "無法啟用LDAP。找不到python-ldap函式庫"
 
-#: rhodecode/controllers/admin/ldap_settings.py:134
+#: rhodecode/controllers/admin/ldap_settings.py:147
 msgid "error occurred during update of ldap settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/permissions.py:56
+#: rhodecode/controllers/admin/permissions.py:59
 msgid "None"
 msgstr "無"
 
-#: rhodecode/controllers/admin/permissions.py:57
+#: rhodecode/controllers/admin/permissions.py:60
 msgid "Read"
 msgstr "讀"
 
-#: rhodecode/controllers/admin/permissions.py:58
+#: rhodecode/controllers/admin/permissions.py:61
 msgid "Write"
 msgstr "寫"
 
-#: rhodecode/controllers/admin/permissions.py:59
+#: rhodecode/controllers/admin/permissions.py:62
 #: rhodecode/templates/admin/ldap/ldap.html:9
 #: rhodecode/templates/admin/permissions/permissions.html:9
 #: rhodecode/templates/admin/repos/repo_add.html:9
 #: rhodecode/templates/admin/repos/repo_edit.html:9
-#: rhodecode/templates/admin/repos/repos.html:10
+#: rhodecode/templates/admin/repos/repos.html:9
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
@@ -307,547 +386,900 @@ msgstr "寫"
 #: rhodecode/templates/admin/settings/settings.html:9
 #: rhodecode/templates/admin/users/user_add.html:8
 #: rhodecode/templates/admin/users/user_edit.html:9
-#: rhodecode/templates/admin/users/user_edit.html:110
+#: rhodecode/templates/admin/users/user_edit.html:122
 #: rhodecode/templates/admin/users/users.html:9
 #: rhodecode/templates/admin/users_groups/users_group_add.html:8
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:9
 #: rhodecode/templates/admin/users_groups/users_groups.html:9
-#: rhodecode/templates/base/base.html:279
-#: rhodecode/templates/base/base.html:366
-#: rhodecode/templates/base/base.html:368
-#: rhodecode/templates/base/base.html:370
+#: rhodecode/templates/base/base.html:197
+#: rhodecode/templates/base/base.html:337
+#: rhodecode/templates/base/base.html:339
+#: rhodecode/templates/base/base.html:341
 msgid "Admin"
 msgstr "管理"
 
-#: rhodecode/controllers/admin/permissions.py:62
+#: rhodecode/controllers/admin/permissions.py:65
 msgid "disabled"
 msgstr "停用"
 
-#: rhodecode/controllers/admin/permissions.py:64
+#: rhodecode/controllers/admin/permissions.py:67
 msgid "allowed with manual account activation"
 msgstr "允許手動啟用帳號"
 
-#: rhodecode/controllers/admin/permissions.py:66
+#: rhodecode/controllers/admin/permissions.py:69
 msgid "allowed with automatic account activation"
 msgstr "允許自動啟用帳號"
 
-#: rhodecode/controllers/admin/permissions.py:68
+#: rhodecode/controllers/admin/permissions.py:71
+#: rhodecode/controllers/admin/permissions.py:74
 msgid "Disabled"
 msgstr "停用"
 
-#: rhodecode/controllers/admin/permissions.py:69
+#: rhodecode/controllers/admin/permissions.py:72
+#: rhodecode/controllers/admin/permissions.py:75
 msgid "Enabled"
 msgstr "啟用"
 
-#: rhodecode/controllers/admin/permissions.py:102
+#: rhodecode/controllers/admin/permissions.py:116
 msgid "Default permissions updated successfully"
 msgstr "預設權限更新完成"
 
-#: rhodecode/controllers/admin/permissions.py:119
+#: rhodecode/controllers/admin/permissions.py:130
 msgid "error occurred during update of permissions"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:96
-#, 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 ""
-
-#: rhodecode/controllers/admin/repos.py:172
+#: rhodecode/controllers/admin/repos.py:123
+msgid "--REMOVE FORK--"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:192
 #, python-format
 msgid "created repository %s from %s"
 msgstr "建立版本庫 %s 到 %s"
 
-#: rhodecode/controllers/admin/repos.py:176
+#: rhodecode/controllers/admin/repos.py:196
 #, python-format
 msgid "created repository %s"
 msgstr "建立版本庫 %s"
 
-#: rhodecode/controllers/admin/repos.py:205
+#: rhodecode/controllers/admin/repos.py:227
 #, python-format
 msgid "error occurred during creation of repository %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:292
+#: rhodecode/controllers/admin/repos.py:319
 #, python-format
 msgid "Cannot delete %s it still contains attached forks"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:320
+#: rhodecode/controllers/admin/repos.py:348
 msgid "An error occurred during deletion of repository user"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:335
-msgid "An error occurred during deletion of repository users groups"
-msgstr ""
-
-#: rhodecode/controllers/admin/repos.py:352
-msgid "An error occurred during deletion of repository stats"
-msgstr ""
-
 #: rhodecode/controllers/admin/repos.py:367
+msgid "An error occurred during deletion of repository users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:385
+msgid "An error occurred during deletion of repository stats"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:402
 msgid "An error occurred during cache invalidation"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:387
+#: rhodecode/controllers/admin/repos.py:422
+msgid "An error occurred during unlocking"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:442
 msgid "Updated repository visibility in public journal"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:390
+#: rhodecode/controllers/admin/repos.py:446
 msgid "An error occurred during setting this repository in public journal"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:395
-#: rhodecode/model/forms.py:53
+#: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
 msgid "Token mismatch"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:408
+#: rhodecode/controllers/admin/repos.py:464
 msgid "Pulled from remote location"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos.py:410
+#: rhodecode/controllers/admin/repos.py:466
 msgid "An error occurred during pull from remote location"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:83
+#: rhodecode/controllers/admin/repos.py:482
+msgid "Nothing"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos.py:484
+#, fuzzy, python-format
+msgid "Marked repo %s as fork of %s"
+msgstr "建立版本庫 %s 到 %s"
+
+#: rhodecode/controllers/admin/repos.py:488
+msgid "An error occurred during this operation"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:116
 #, python-format
 msgid "created repos group %s"
 msgstr "建立版本庫群組 %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:96
+#: rhodecode/controllers/admin/repos_groups.py:129
 #, python-format
 msgid "error occurred during creation of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:130
+#: rhodecode/controllers/admin/repos_groups.py:163
 #, python-format
 msgid "updated repos group %s"
 msgstr "更新版本庫群組 %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:143
+#: rhodecode/controllers/admin/repos_groups.py:176
 #, python-format
 msgid "error occurred during update of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:164
+#: rhodecode/controllers/admin/repos_groups.py:194
 #, python-format
 msgid "This group contains %s repositores and cannot be deleted"
 msgstr ""
 
-#: rhodecode/controllers/admin/repos_groups.py:171
+#: rhodecode/controllers/admin/repos_groups.py:202
 #, python-format
 msgid "removed repos group %s"
 msgstr "移除版本庫群組 %s"
 
-#: rhodecode/controllers/admin/repos_groups.py:175
+#: rhodecode/controllers/admin/repos_groups.py:208
+msgid "Cannot delete this group it still contains subgroups"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:213
+#: rhodecode/controllers/admin/repos_groups.py:218
 #, python-format
 msgid "error occurred during deletion of repos group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:109
+#: rhodecode/controllers/admin/repos_groups.py:238
+msgid "An error occurred during deletion of group user"
+msgstr ""
+
+#: rhodecode/controllers/admin/repos_groups.py:258
+msgid "An error occurred during deletion of group users groups"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:121
 #, python-format
 msgid "Repositories successfully rescanned added: %s,removed: %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:118
+#: rhodecode/controllers/admin/settings.py:129
 msgid "Whoosh reindex task scheduled"
 msgstr "Whoosh 重新索引工作排程"
 
-#: rhodecode/controllers/admin/settings.py:143
+#: rhodecode/controllers/admin/settings.py:160
 msgid "Updated application settings"
 msgstr "更新應用設定"
 
-#: rhodecode/controllers/admin/settings.py:148
-#: rhodecode/controllers/admin/settings.py:215
+#: rhodecode/controllers/admin/settings.py:164
+#: rhodecode/controllers/admin/settings.py:275
 msgid "error occurred during updating application settings"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:210
-msgid "Updated mercurial settings"
+#: rhodecode/controllers/admin/settings.py:200
+#, fuzzy
+msgid "Updated visualisation settings"
+msgstr "更新應用設定"
+
+#: rhodecode/controllers/admin/settings.py:205
+msgid "error occurred during updating visualisation settings"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:271
+#, fuzzy
+msgid "Updated VCS settings"
 msgstr "更新 mercurial 設定"
 
-#: rhodecode/controllers/admin/settings.py:236
+#: rhodecode/controllers/admin/settings.py:285
 msgid "Added new hook"
 msgstr "新增hook"
 
-#: rhodecode/controllers/admin/settings.py:247
+#: rhodecode/controllers/admin/settings.py:297
 msgid "Updated hooks"
 msgstr "更新hook"
 
-#: rhodecode/controllers/admin/settings.py:251
+#: rhodecode/controllers/admin/settings.py:301
 msgid "error occurred during hook creation"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:310
+#: rhodecode/controllers/admin/settings.py:320
+msgid "Email task created"
+msgstr ""
+
+#: rhodecode/controllers/admin/settings.py:375
 msgid "You can't edit this user since it's crucial for entire application"
 msgstr ""
 
-#: rhodecode/controllers/admin/settings.py:339
+#: rhodecode/controllers/admin/settings.py:406
 msgid "Your account was updated successfully"
 msgstr "您的帳號已更新完成"
 
-#: rhodecode/controllers/admin/settings.py:359
-#: rhodecode/controllers/admin/users.py:130
+#: rhodecode/controllers/admin/settings.py:421
+#: rhodecode/controllers/admin/users.py:191
 #, python-format
 msgid "error occurred during update of user %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:78
+#: rhodecode/controllers/admin/users.py:130
 #, python-format
 msgid "created user %s"
 msgstr "建立使用者 %s"
 
-#: rhodecode/controllers/admin/users.py:90
+#: rhodecode/controllers/admin/users.py:142
 #, python-format
 msgid "error occurred during creation of user %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:116
+#: rhodecode/controllers/admin/users.py:171
 msgid "User updated successfully"
 msgstr "使用者更新完成"
 
-#: rhodecode/controllers/admin/users.py:146
+#: rhodecode/controllers/admin/users.py:207
 msgid "successfully deleted user"
 msgstr "成功刪除使用者"
 
-#: rhodecode/controllers/admin/users.py:150
+#: rhodecode/controllers/admin/users.py:212
 msgid "An error occurred during deletion of user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:166
+#: rhodecode/controllers/admin/users.py:226
 msgid "You can't edit this user"
 msgstr "您無法編輯這位使用者"
 
-#: rhodecode/controllers/admin/users.py:195
-#: rhodecode/controllers/admin/users_groups.py:202
+#: rhodecode/controllers/admin/users.py:266
 msgid "Granted 'repository create' permission to user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users.py:204
-#: rhodecode/controllers/admin/users_groups.py:211
+#: rhodecode/controllers/admin/users.py:271
 msgid "Revoked 'repository create' permission to user"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:74
+#: rhodecode/controllers/admin/users.py:277
+#, fuzzy
+msgid "Granted 'repository fork' permission to user"
+msgstr "版本庫權限"
+
+#: rhodecode/controllers/admin/users.py:282
+#, fuzzy
+msgid "Revoked 'repository fork' permission to user"
+msgstr "版本庫權限"
+
+#: rhodecode/controllers/admin/users.py:288
+#: rhodecode/controllers/admin/users_groups.py:255
+msgid "An error occurred during permissions saving"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:303
+#, python-format
+msgid "Added email %s to user"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:309
+msgid "An error occurred during email saving"
+msgstr ""
+
+#: rhodecode/controllers/admin/users.py:319
+#, fuzzy
+msgid "Removed email from user"
+msgstr "移除版本庫群組 %s"
+
+#: rhodecode/controllers/admin/users_groups.py:84
 #, python-format
 msgid "created users group %s"
 msgstr "建立使用者群組 %s"
 
-#: rhodecode/controllers/admin/users_groups.py:86
+#: rhodecode/controllers/admin/users_groups.py:95
 #, python-format
 msgid "error occurred during creation of users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:119
+#: rhodecode/controllers/admin/users_groups.py:135
 #, python-format
 msgid "updated users group %s"
 msgstr "更新使用者群組 %s"
 
-#: rhodecode/controllers/admin/users_groups.py:138
+#: rhodecode/controllers/admin/users_groups.py:157
 #, python-format
 msgid "error occurred during update of users group %s"
 msgstr ""
 
-#: rhodecode/controllers/admin/users_groups.py:154
+#: rhodecode/controllers/admin/users_groups.py:174
 msgid "successfully deleted users group"
 msgstr "成功移除使用者群組"
 
-#: rhodecode/controllers/admin/users_groups.py:158
+#: rhodecode/controllers/admin/users_groups.py:179
 msgid "An error occurred during deletion of users group"
 msgstr ""
 
-#: rhodecode/lib/__init__.py:279
-msgid "year"
-msgstr "年"
-
-#: rhodecode/lib/__init__.py:280
-msgid "month"
-msgstr "月"
-
-#: rhodecode/lib/__init__.py:281
-msgid "day"
-msgstr "日"
-
-#: rhodecode/lib/__init__.py:282
-msgid "hour"
-msgstr "時"
-
-#: rhodecode/lib/__init__.py:283
-msgid "minute"
-msgstr "分"
-
-#: rhodecode/lib/__init__.py:284
-msgid "second"
-msgstr "秒"
-
-#: rhodecode/lib/__init__.py:293
-msgid "ago"
-msgstr "之前"
-
-#: rhodecode/lib/__init__.py:296
-msgid "just now"
-msgstr "現在"
-
-#: rhodecode/lib/auth.py:377
+#: rhodecode/controllers/admin/users_groups.py:233
+msgid "Granted 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:238
+msgid "Revoked 'repository create' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:244
+msgid "Granted 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/controllers/admin/users_groups.py:249
+msgid "Revoked 'repository fork' permission to users group"
+msgstr ""
+
+#: rhodecode/lib/auth.py:499
 msgid "You need to be a registered user to perform this action"
 msgstr "您必須是註冊使用者才能執行這個動作"
 
-#: rhodecode/lib/auth.py:421
+#: rhodecode/lib/auth.py:540
 msgid "You need to be a signed in to view this page"
 msgstr "您必須登入後才能瀏覽這個頁面"
 
-#: rhodecode/lib/helpers.py:307
+#: rhodecode/lib/diffs.py:86
+msgid "Changeset was too big and was cut off, use diff menu to display this diff"
+msgstr ""
+
+#: rhodecode/lib/diffs.py:96
+msgid "No changes detected"
+msgstr "尚未有任何變更"
+
+#: rhodecode/lib/helpers.py:372
+#, python-format
+msgid "%a, %d %b %Y %H:%M:%S"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:484
 msgid "True"
 msgstr "真"
 
-#: rhodecode/lib/helpers.py:311
+#: rhodecode/lib/helpers.py:488
 msgid "False"
 msgstr "假"
 
-#: rhodecode/lib/helpers.py:352
+#: rhodecode/lib/helpers.py:532
+#, fuzzy
+msgid "Changeset not found"
+msgstr "修改"
+
+#: rhodecode/lib/helpers.py:555
 #, python-format
 msgid "Show all combined changesets %s->%s"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:356
+#: rhodecode/lib/helpers.py:561
 msgid "compare view"
 msgstr ""
 
-#: rhodecode/lib/helpers.py:365
-msgid "and"
-msgstr "和"
-
-#: rhodecode/lib/helpers.py:365
-#, python-format
-msgid "%s more"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:367
-#: rhodecode/templates/changelog/changelog.html:14
-#: rhodecode/templates/changelog/changelog.html:39
-msgid "revisions"
-msgstr "修訂"
-
-#: rhodecode/lib/helpers.py:385
-msgid "fork name "
-msgstr "fork 名稱"
-
-#: rhodecode/lib/helpers.py:388
-msgid "[deleted] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:389
-#: rhodecode/lib/helpers.py:393
-msgid "[created] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:390
-#: rhodecode/lib/helpers.py:394
-msgid "[forked] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:391
-#: rhodecode/lib/helpers.py:395
-msgid "[updated] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:392
-msgid "[delete] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:396
-msgid "[pushed] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:397
-msgid "[committed via RhodeCode] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:398
-msgid "[pulled from remote] into"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:399
-msgid "[pulled] from"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:400
-msgid "[started following] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:401
-msgid "[stopped following] repository"
-msgstr ""
-
-#: rhodecode/lib/helpers.py:577
-#, python-format
-msgid " and %s more"
-msgstr ""
-
 #: rhodecode/lib/helpers.py:581
+msgid "and"
+msgstr "和"
+
+#: rhodecode/lib/helpers.py:582
+#, python-format
+msgid "%s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
+msgid "revisions"
+msgstr "修訂"
+
+#: rhodecode/lib/helpers.py:606
+msgid "fork name "
+msgstr "fork 名稱"
+
+#: rhodecode/lib/helpers.py:620
+#: rhodecode/templates/pullrequests/pullrequest_show.html:4
+#: rhodecode/templates/pullrequests/pullrequest_show.html:12
+#, python-format
+msgid "Pull request #%s"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:626
+msgid "[deleted] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
+msgid "[created] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:630
+#, fuzzy
+msgid "[created] repository as fork"
+msgstr "建立版本庫 %s"
+
+#: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
+msgid "[forked] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
+msgid "[updated] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:636
+msgid "[delete] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:644
+#, fuzzy
+msgid "[created] user"
+msgstr "建立使用者 %s"
+
+#: rhodecode/lib/helpers.py:646
+#, fuzzy
+msgid "[updated] user"
+msgstr "更新使用者群組 %s"
+
+#: rhodecode/lib/helpers.py:648
+#, fuzzy
+msgid "[created] users group"
+msgstr "建立使用者群組 %s"
+
+#: rhodecode/lib/helpers.py:650
+#, fuzzy
+msgid "[updated] users group"
+msgstr "更新使用者群組 %s"
+
+#: rhodecode/lib/helpers.py:652
+msgid "[commented] on revision in repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:654
+#, fuzzy
+msgid "[commented] on pull request for"
+msgstr "建立使用者 %s"
+
+#: rhodecode/lib/helpers.py:656
+msgid "[closed] pull request for"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:658
+msgid "[pushed] into"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:660
+msgid "[committed via RhodeCode] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:662
+msgid "[pulled from remote] into repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:664
+msgid "[pulled] from"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:666
+msgid "[started following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:668
+msgid "[stopped following] repository"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:840
+#, python-format
+msgid " and %s more"
+msgstr ""
+
+#: rhodecode/lib/helpers.py:844
 msgid "No Files"
 msgstr "沒有檔案"
 
-#: rhodecode/model/forms.py:66
-msgid "Invalid username"
-msgstr "無效的使用者名稱"
-
-#: rhodecode/model/forms.py:75
-msgid "This username already exists"
+#: rhodecode/lib/utils2.py:335
+#, fuzzy, python-format
+msgid "%d year"
+msgid_plural "%d years"
+msgstr[0] "年"
+
+#: rhodecode/lib/utils2.py:336
+#, fuzzy, python-format
+msgid "%d month"
+msgid_plural "%d months"
+msgstr[0] "月"
+
+#: rhodecode/lib/utils2.py:337
+#, fuzzy, python-format
+msgid "%d day"
+msgid_plural "%d days"
+msgstr[0] "日"
+
+#: rhodecode/lib/utils2.py:338
+#, fuzzy, python-format
+msgid "%d hour"
+msgid_plural "%d hours"
+msgstr[0] "時"
+
+#: rhodecode/lib/utils2.py:339
+#, fuzzy, python-format
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] "分"
+
+#: rhodecode/lib/utils2.py:340
+#, fuzzy, python-format
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] "秒"
+
+#: rhodecode/lib/utils2.py:355
+#, fuzzy, python-format
+msgid "%s ago"
+msgstr "之前"
+
+#: rhodecode/lib/utils2.py:357
+#, python-format
+msgid "%s and %s ago"
+msgstr ""
+
+#: rhodecode/lib/utils2.py:360
+msgid "just now"
+msgstr "現在"
+
+#: rhodecode/lib/celerylib/tasks.py:269
+#, fuzzy
+msgid "password reset link"
+msgstr "您的密碼重設連結已寄出"
+
+#: rhodecode/model/comment.py:110
+#, python-format
+msgid "on line %s"
+msgstr ""
+
+#: rhodecode/model/comment.py:157
+msgid "[Mention]"
+msgstr ""
+
+#: rhodecode/model/db.py:1140
+#, fuzzy
+msgid "Repository no access"
+msgstr "個版本庫"
+
+#: rhodecode/model/db.py:1141
+#, fuzzy
+msgid "Repository read access"
+msgstr "這個版本庫已經存在"
+
+#: rhodecode/model/db.py:1142
+#, fuzzy
+msgid "Repository write access"
+msgstr "個版本庫"
+
+#: rhodecode/model/db.py:1143
+#, fuzzy
+msgid "Repository admin access"
+msgstr "個版本庫"
+
+#: rhodecode/model/db.py:1145
+#, fuzzy
+msgid "Repositories Group no access"
+msgstr "版本庫群組"
+
+#: rhodecode/model/db.py:1146
+#, fuzzy
+msgid "Repositories Group read access"
+msgstr "版本庫群組"
+
+#: rhodecode/model/db.py:1147
+#, fuzzy
+msgid "Repositories Group write access"
+msgstr "版本庫群組"
+
+#: rhodecode/model/db.py:1148
+#, fuzzy
+msgid "Repositories Group admin access"
+msgstr "版本庫群組"
+
+#: rhodecode/model/db.py:1150
+#, fuzzy
+msgid "RhodeCode Administrator"
+msgstr "使用者管理員"
+
+#: rhodecode/model/db.py:1151
+#, fuzzy
+msgid "Repository creation disabled"
+msgstr "版本庫建立"
+
+#: rhodecode/model/db.py:1152
+#, fuzzy
+msgid "Repository creation enabled"
+msgstr "版本庫建立"
+
+#: rhodecode/model/db.py:1153
+#, fuzzy
+msgid "Repository forking disabled"
+msgstr "版本庫建立"
+
+#: rhodecode/model/db.py:1154
+#, fuzzy
+msgid "Repository forking enabled"
+msgstr "版本庫建立"
+
+#: rhodecode/model/db.py:1155
+#, fuzzy
+msgid "Register disabled"
+msgstr "停用"
+
+#: rhodecode/model/db.py:1156
+msgid "Register new user with RhodeCode with manual activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1159
+msgid "Register new user with RhodeCode with auto activation"
+msgstr ""
+
+#: rhodecode/model/db.py:1579
+msgid "Not Reviewed"
+msgstr ""
+
+#: rhodecode/model/db.py:1580
+#, fuzzy
+msgid "Approved"
+msgstr "移除"
+
+#: rhodecode/model/db.py:1581
+msgid "Rejected"
+msgstr ""
+
+#: rhodecode/model/db.py:1582
+msgid "Under Review"
+msgstr ""
+
+#: rhodecode/model/forms.py:43
+msgid "Please enter a login"
+msgstr "請登入"
+
+#: rhodecode/model/forms.py:44
+#, python-format
+msgid "Enter a value %(min)i characters long or more"
+msgstr ""
+
+#: rhodecode/model/forms.py:52
+msgid "Please enter a password"
+msgstr "請輸入密碼"
+
+#: rhodecode/model/forms.py:53
+#, python-format
+msgid "Enter %(min)i characters or more"
+msgstr ""
+
+#: rhodecode/model/notification.py:220
+msgid "commented on commit"
+msgstr ""
+
+#: rhodecode/model/notification.py:221
+#, fuzzy
+msgid "sent message"
+msgstr "遞交資訊"
+
+#: rhodecode/model/notification.py:222
+msgid "mentioned you"
+msgstr ""
+
+#: rhodecode/model/notification.py:223
+#, fuzzy
+msgid "registered in RhodeCode"
+msgstr "您已經成功註冊rhodecode"
+
+#: rhodecode/model/notification.py:224
+msgid "opened new pull request"
+msgstr ""
+
+#: rhodecode/model/notification.py:225
+msgid "commented on pull request"
+msgstr ""
+
+#: rhodecode/model/pull_request.py:84
+#, python-format
+msgid "%(user)s wants you to review pull request #%(pr_id)s"
+msgstr ""
+
+#: rhodecode/model/scm.py:535
+#, fuzzy
+msgid "latest tip"
+msgstr "最後登入"
+
+#: rhodecode/model/user.py:230
+#, fuzzy
+msgid "new user registration"
+msgstr "[RhodeCode] 新使用者註冊"
+
+#: rhodecode/model/user.py:255 rhodecode/model/user.py:277
+#: rhodecode/model/user.py:299
+msgid "You can't Edit this user since it's crucial for entire application"
+msgstr "您無法編輯這個使用者,因為他是系統帳號"
+
+#: rhodecode/model/user.py:323
+msgid "You can't remove this user since it's crucial for entire application"
+msgstr "您無法移除這個使用者,因為他是系統帳號"
+
+#: rhodecode/model/user.py:329
+#, fuzzy, python-format
+msgid ""
+"user \"%s\" still owns %s repositories and cannot be removed. Switch "
+"owners or remove those repositories. %s"
+msgstr "這個使用者擁有 %s 個版本庫所以無法移除,請先變更版本庫擁有者或者刪除版本庫"
+
+#: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
+msgid "Value cannot be an empty list"
+msgstr ""
+
+#: rhodecode/model/validators.py:82
+#, fuzzy, python-format
+msgid "Username \"%(username)s\" already exists"
 msgstr "使用者名稱已存在"
 
-#: rhodecode/model/forms.py:79
-msgid "Username may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
+#: rhodecode/model/validators.py:84
+#, python-format
+msgid "Username \"%(username)s\" is forbidden"
+msgstr ""
+
+#: rhodecode/model/validators.py:86
+msgid ""
+"Username may only contain alphanumeric characters underscores, periods or"
+" dashes and must begin with alphanumeric character"
 msgstr "使用者名稱只能使用字母數字、底線、小數點或破折號,且必須使用數字或字母開頭"
 
-#: rhodecode/model/forms.py:94
-msgid "Invalid group name"
-msgstr "無效的群組名稱"
-
-#: rhodecode/model/forms.py:104
-msgid "This users group already exists"
+#: rhodecode/model/validators.py:114
+#, fuzzy, python-format
+msgid "Username %(username)s is not valid"
+msgstr "使用者名稱或群組名稱無效"
+
+#: rhodecode/model/validators.py:133
+#, fuzzy
+msgid "Invalid users group name"
+msgstr "無效的使用者名稱"
+
+#: rhodecode/model/validators.py:134
+#, fuzzy, python-format
+msgid "Users group \"%(usersgroup)s\" already exists"
 msgstr "這個使用者群組已存在"
 
-#: rhodecode/model/forms.py:110
-msgid "Group name may only contain alphanumeric characters underscores, periods or dashes and must begin with alphanumeric character"
+#: rhodecode/model/validators.py:136
+msgid ""
+"users group name may only contain  alphanumeric characters underscores, "
+"periods or dashes and must begin with alphanumeric character"
 msgstr "群組名稱只能使用字母數字、底線、小數點或破折號,且必須使用數字或字母開頭"
 
-#: rhodecode/model/forms.py:132
+#: rhodecode/model/validators.py:174
 msgid "Cannot assign this group as parent"
 msgstr ""
 
-#: rhodecode/model/forms.py:148
-msgid "This group already exists"
-msgstr "這個群組已存在"
-
-#: rhodecode/model/forms.py:164
-#: rhodecode/model/forms.py:172
-#: rhodecode/model/forms.py:180
-msgid "Invalid characters in password"
+#: rhodecode/model/validators.py:175
+#, fuzzy, python-format
+msgid "Group \"%(group_name)s\" already exists"
+msgstr "使用者名稱已存在"
+
+#: rhodecode/model/validators.py:177
+#, fuzzy, python-format
+msgid "Repository with name \"%(group_name)s\" already exists"
+msgstr "這個版本庫已經存在"
+
+#: rhodecode/model/validators.py:235
+#, fuzzy
+msgid "Invalid characters (non-ascii) in password"
 msgstr "無效的字元在密碼中"
 
-#: rhodecode/model/forms.py:191
+#: rhodecode/model/validators.py:250
 msgid "Passwords do not match"
 msgstr "密碼不相符"
 
-#: rhodecode/model/forms.py:196
+#: rhodecode/model/validators.py:267
 msgid "invalid password"
 msgstr "無效的密碼"
 
-#: rhodecode/model/forms.py:197
+#: rhodecode/model/validators.py:268
 msgid "invalid user name"
 msgstr "無效的使用者名稱"
 
-#: rhodecode/model/forms.py:198
+#: rhodecode/model/validators.py:269
 msgid "Your account is disabled"
 msgstr "您的帳號已被停用"
 
-#: rhodecode/model/forms.py:233
-msgid "This username is not valid"
-msgstr "無效的使用者名稱"
-
-#: rhodecode/model/forms.py:245
-msgid "This repository name is disallowed"
+#: rhodecode/model/validators.py:313
+#, fuzzy, python-format
+msgid "Repository name %(repo)s is disallowed"
 msgstr "不允許的版本庫名稱"
 
-#: rhodecode/model/forms.py:266
-#, python-format
-msgid "This repository already exists in group \"%s\""
+#: rhodecode/model/validators.py:315
+#, fuzzy, python-format
+msgid "Repository named %(repo)s already exists"
+msgstr "這個版本庫已經存在"
+
+#: rhodecode/model/validators.py:316
+#, fuzzy, python-format
+msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
 msgstr "這個版本庫已存在於群組 \"%s\""
 
-#: rhodecode/model/forms.py:274
-msgid "This repository already exists"
+#: rhodecode/model/validators.py:318
+#, fuzzy, python-format
+msgid "Repositories group with name \"%(repo)s\" already exists"
 msgstr "這個版本庫已經存在"
 
-#: rhodecode/model/forms.py:312
-#: rhodecode/model/forms.py:319
+#: rhodecode/model/validators.py:431
 msgid "invalid clone url"
 msgstr "無效的複製URL"
 
-#: rhodecode/model/forms.py:322
-msgid "Invalid clone url, provide a valid clone http\\s url"
-msgstr ""
-
-#: rhodecode/model/forms.py:334
-msgid "Fork have to be the same type as original"
+#: rhodecode/model/validators.py:432
+msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
+msgstr ""
+
+#: rhodecode/model/validators.py:457
+#, fuzzy
+msgid "Fork have to be the same type as parent"
 msgstr "Fork 必須使用相同的版本庫類型"
 
-#: rhodecode/model/forms.py:341
+#: rhodecode/model/validators.py:478
 msgid "This username or users group name is not valid"
 msgstr "使用者名稱或群組名稱無效"
 
-#: rhodecode/model/forms.py:403
+#: rhodecode/model/validators.py:562
 msgid "This is not a valid path"
 msgstr "不是一個有效的路徑"
 
-#: rhodecode/model/forms.py:416
+#: rhodecode/model/validators.py:577
 msgid "This e-mail address is already taken"
 msgstr "這個郵件位址已經使用了"
 
-#: rhodecode/model/forms.py:427
-msgid "This e-mail address doesn't exist."
+#: rhodecode/model/validators.py:597
+#, fuzzy, python-format
+msgid "e-mail \"%(email)s\" does not exist."
 msgstr "這個郵件位址不存在"
 
-#: rhodecode/model/forms.py:447
-msgid "The LDAP Login attribute of the CN must be specified - this is the name of the attribute that is equivalent to 'username'"
-msgstr ""
-
-#: rhodecode/model/forms.py:466
-msgid "Please enter a login"
-msgstr "請登入"
-
-#: rhodecode/model/forms.py:467
-#, python-format
-msgid "Enter a value %(min)i characters long or more"
-msgstr ""
-
-#: rhodecode/model/forms.py:475
-msgid "Please enter a password"
-msgstr "請輸入密碼"
-
-#: rhodecode/model/forms.py:476
+#: rhodecode/model/validators.py:634
+msgid ""
+"The LDAP Login attribute of the CN must be specified - this is the name "
+"of the attribute that is equivalent to \"username\""
+msgstr ""
+
+#: rhodecode/model/validators.py:653
 #, python-format
-msgid "Enter %(min)i characters or more"
-msgstr ""
-
-#: rhodecode/model/user.py:145
-msgid "[RhodeCode] New User registration"
-msgstr "[RhodeCode] 新使用者註冊"
-
-#: rhodecode/model/user.py:157
-#: rhodecode/model/user.py:179
-msgid "You can't Edit this user since it's crucial for entire application"
-msgstr "您無法編輯這個使用者,因為他是系統帳號"
-
-#: rhodecode/model/user.py:201
-msgid "You can't remove this user since it's crucial for entire application"
-msgstr "您無法移除這個使用者,因為他是系統帳號"
-
-#: rhodecode/model/user.py:204
-#, python-format
-msgid "This user still owns %s repositories and cannot be removed. Switch owners or remove those repositories"
-msgstr "這個使用者擁有 %s 個版本庫所以無法移除,請先變更版本庫擁有者或者刪除版本庫"
-
-#: rhodecode/templates/index.html:4
+msgid "Revisions %(revs)s are already part of pull request or have set status"
+msgstr ""
+
+#: rhodecode/templates/index.html:3
 msgid "Dashboard"
 msgstr "儀表板"
 
-#: rhodecode/templates/index_base.html:22
-#: rhodecode/templates/admin/users/user_edit_my_account.html:102
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/repo_switcher_list.html:4
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/admin/users/user_edit_my_account.html:31
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/bookmarks/bookmarks.html:10
+#: rhodecode/templates/branches/branches.html:9
+#: rhodecode/templates/journal/journal.html:40
+#: rhodecode/templates/tags/tags.html:10
 msgid "quick filter..."
 msgstr "快速過濾..."
 
-#: rhodecode/templates/index_base.html:23
-#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/index_base.html:6
+#: rhodecode/templates/admin/repos/repos.html:9
+#: rhodecode/templates/base/base.html:221
 msgid "repositories"
 msgstr "個版本庫"
 
+#: rhodecode/templates/index_base.html:13
+#: rhodecode/templates/index_base.html:15
+#: rhodecode/templates/admin/repos/repos.html:21
+msgid "ADD REPOSITORY"
+msgstr "新增版本庫"
+
 #: rhodecode/templates/index_base.html:29
-#: rhodecode/templates/admin/repos/repos.html:22
-msgid "ADD NEW REPOSITORY"
-msgstr "新增版本庫"
-
-#: rhodecode/templates/index_base.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
@@ -856,159 +1288,161 @@ msgstr "新增版本庫"
 msgid "Group name"
 msgstr "群組名稱"
 
-#: rhodecode/templates/index_base.html:42
-#: rhodecode/templates/index_base.html:73
-#: rhodecode/templates/admin/repos/repo_add_base.html:44
-#: rhodecode/templates/admin/repos/repo_edit.html:64
-#: rhodecode/templates/admin/repos/repos.html:31
+#: rhodecode/templates/index_base.html:30
+#: rhodecode/templates/index_base.html:71
+#: rhodecode/templates/index_base.html:142
+#: rhodecode/templates/index_base.html:168
+#: rhodecode/templates/admin/repos/repo_add_base.html:56
+#: rhodecode/templates/admin/repos/repo_edit.html:75
+#: rhodecode/templates/admin/repos/repos.html:72
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
-#: rhodecode/templates/settings/repo_fork.html:40
-#: rhodecode/templates/settings/repo_settings.html:40
-#: rhodecode/templates/summary/summary.html:92
+#: rhodecode/templates/forks/fork.html:59
+#: rhodecode/templates/settings/repo_settings.html:66
+#: rhodecode/templates/summary/summary.html:105
 msgid "Description"
 msgstr "描述"
 
-#: rhodecode/templates/index_base.html:53
+#: rhodecode/templates/index_base.html:40
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
 msgid "Repositories group"
 msgstr "版本庫群組"
 
-#: rhodecode/templates/index_base.html:72
+#: rhodecode/templates/index_base.html:70
+#: rhodecode/templates/index_base.html:166
 #: rhodecode/templates/admin/repos/repo_add_base.html:9
 #: rhodecode/templates/admin/repos/repo_edit.html:32
-#: rhodecode/templates/admin/repos/repos.html:30
-#: rhodecode/templates/admin/users/user_edit_my_account.html:117
-#: rhodecode/templates/files/files_browser.html:157
+#: rhodecode/templates/admin/repos/repos.html:70
+#: rhodecode/templates/admin/users/user_edit.html:192
+#: rhodecode/templates/admin/users/user_edit_my_account.html:59
+#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
+#: rhodecode/templates/bookmarks/bookmarks.html:36
+#: rhodecode/templates/bookmarks/bookmarks_data.html:6
+#: rhodecode/templates/branches/branches.html:51
+#: rhodecode/templates/files/files_browser.html:47
+#: rhodecode/templates/journal/journal.html:59
+#: rhodecode/templates/journal/journal.html:107
+#: rhodecode/templates/journal/journal.html:186
 #: rhodecode/templates/settings/repo_settings.html:31
-#: rhodecode/templates/summary/summary.html:31
-#: rhodecode/templates/summary/summary.html:107
+#: rhodecode/templates/summary/summary.html:43
+#: rhodecode/templates/summary/summary.html:123
+#: rhodecode/templates/tags/tags.html:36
+#: rhodecode/templates/tags/tags_data.html:6
 msgid "Name"
 msgstr "名稱"
 
-#: rhodecode/templates/index_base.html:74
-#: rhodecode/templates/admin/repos/repos.html:32
-#: rhodecode/templates/summary/summary.html:114
+#: rhodecode/templates/index_base.html:72
 msgid "Last change"
 msgstr "最後修改"
 
+#: rhodecode/templates/index_base.html:73
+#: rhodecode/templates/index_base.html:171
+#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/journal/journal.html:188
+msgid "Tip"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:74
+#: rhodecode/templates/index_base.html:173
+#: rhodecode/templates/admin/repos/repo_edit.html:121
+#: rhodecode/templates/admin/repos/repos.html:73
+msgid "Owner"
+msgstr "擁有者"
+
 #: rhodecode/templates/index_base.html:75
-#: rhodecode/templates/admin/repos/repos.html:33
-msgid "Tip"
+#: rhodecode/templates/summary/summary.html:48
+#: rhodecode/templates/summary/summary.html:51
+msgid "RSS"
 msgstr ""
 
 #: rhodecode/templates/index_base.html:76
-#: rhodecode/templates/admin/repos/repo_edit.html:97
-msgid "Owner"
-msgstr "擁有者"
-
-#: rhodecode/templates/index_base.html:77
-#: rhodecode/templates/journal/public_journal.html:20
-#: rhodecode/templates/summary/summary.html:180
-#: rhodecode/templates/summary/summary.html:183
-msgid "RSS"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:78
-#: rhodecode/templates/journal/public_journal.html:23
-#: rhodecode/templates/summary/summary.html:181
-#: rhodecode/templates/summary/summary.html:184
 msgid "Atom"
 msgstr ""
 
-#: rhodecode/templates/index_base.html:87
-#: rhodecode/templates/index_base.html:89
-#: rhodecode/templates/index_base.html:91
-#: rhodecode/templates/base/base.html:209
-#: rhodecode/templates/base/base.html:211
-#: rhodecode/templates/base/base.html:213
-#: rhodecode/templates/summary/summary.html:4
-msgid "Summary"
-msgstr "概況"
-
-#: rhodecode/templates/index_base.html:95
-#: rhodecode/templates/index_base.html:97
-#: rhodecode/templates/index_base.html:99
-#: rhodecode/templates/base/base.html:225
-#: rhodecode/templates/base/base.html:227
-#: rhodecode/templates/base/base.html:229
-#: rhodecode/templates/changelog/changelog.html:6
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "Changelog"
-msgstr "修改紀錄"
-
-#: rhodecode/templates/index_base.html:103
-#: rhodecode/templates/index_base.html:105
-#: rhodecode/templates/index_base.html:107
-#: rhodecode/templates/base/base.html:268
-#: rhodecode/templates/base/base.html:270
-#: rhodecode/templates/base/base.html:272
-#: rhodecode/templates/files/files.html:4
-msgid "Files"
-msgstr "檔案"
-
-#: rhodecode/templates/index_base.html:116
-#: rhodecode/templates/admin/repos/repos.html:42
-#: rhodecode/templates/admin/users/user_edit_my_account.html:127
-#: rhodecode/templates/summary/summary.html:48
-msgid "Mercurial repository"
-msgstr "Mercurial 版本庫"
-
-#: rhodecode/templates/index_base.html:118
-#: rhodecode/templates/admin/repos/repos.html:44
-#: rhodecode/templates/admin/users/user_edit_my_account.html:129
-#: rhodecode/templates/summary/summary.html:51
-msgid "Git repository"
-msgstr "Git 版本庫"
-
-#: rhodecode/templates/index_base.html:123
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
-#: rhodecode/templates/journal/journal.html:53
-#: rhodecode/templates/summary/summary.html:56
-msgid "private repository"
-msgstr "私有版本庫"
-
-#: rhodecode/templates/index_base.html:125
-#: rhodecode/templates/journal/journal.html:55
-#: rhodecode/templates/summary/summary.html:58
-msgid "public repository"
-msgstr "公開版本庫"
-
-#: rhodecode/templates/index_base.html:133
-#: rhodecode/templates/base/base.html:291
-#: rhodecode/templates/settings/repo_fork.html:13
-msgid "fork"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:134
-#: rhodecode/templates/admin/repos/repos.html:60
-#: rhodecode/templates/admin/users/user_edit_my_account.html:143
-#: rhodecode/templates/summary/summary.html:69
-#: rhodecode/templates/summary/summary.html:71
-msgid "Fork of"
-msgstr ""
-
-#: rhodecode/templates/index_base.html:155
-#: rhodecode/templates/admin/repos/repos.html:73
-msgid "No changesets yet"
-msgstr "尚未有任何變更"
-
-#: rhodecode/templates/index_base.html:161
-#: rhodecode/templates/index_base.html:163
+#: rhodecode/templates/index_base.html:110
+#: rhodecode/templates/index_base.html:112
 #, python-format
 msgid "Subscribe to %s rss feed"
 msgstr "訂閱 %s rss"
 
-#: rhodecode/templates/index_base.html:168
-#: rhodecode/templates/index_base.html:170
+#: rhodecode/templates/index_base.html:117
+#: rhodecode/templates/index_base.html:119
 #, python-format
 msgid "Subscribe to %s atom feed"
 msgstr "訂閱 %s atom"
 
-#: rhodecode/templates/login.html:5
-#: rhodecode/templates/login.html:54
-#: rhodecode/templates/base/base.html:38
+#: rhodecode/templates/index_base.html:140
+#, fuzzy
+msgid "Group Name"
+msgstr "群組名稱"
+
+#: rhodecode/templates/index_base.html:158
+#: rhodecode/templates/index_base.html:198
+#: rhodecode/templates/admin/repos/repos.html:94
+#: rhodecode/templates/admin/users/user_edit_my_account.html:179
+#: rhodecode/templates/admin/users/users.html:107
+#: rhodecode/templates/bookmarks/bookmarks.html:60
+#: rhodecode/templates/branches/branches.html:77
+#: rhodecode/templates/journal/journal.html:211
+#: rhodecode/templates/tags/tags.html:60
+msgid "Click to sort ascending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:159
+#: rhodecode/templates/index_base.html:199
+#: rhodecode/templates/admin/repos/repos.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account.html:180
+#: rhodecode/templates/admin/users/users.html:108
+#: rhodecode/templates/bookmarks/bookmarks.html:61
+#: rhodecode/templates/branches/branches.html:78
+#: rhodecode/templates/journal/journal.html:212
+#: rhodecode/templates/tags/tags.html:61
+msgid "Click to sort descending"
+msgstr ""
+
+#: rhodecode/templates/index_base.html:169
+#, fuzzy
+msgid "Last Change"
+msgstr "最後修改"
+
+#: rhodecode/templates/index_base.html:200
+#: rhodecode/templates/admin/repos/repos.html:96
+#: rhodecode/templates/admin/users/user_edit_my_account.html:181
+#: rhodecode/templates/admin/users/users.html:109
+#: rhodecode/templates/bookmarks/bookmarks.html:62
+#: rhodecode/templates/branches/branches.html:79
+#: rhodecode/templates/journal/journal.html:213
+#: rhodecode/templates/tags/tags.html:62
+msgid "No records found."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:201
+#: rhodecode/templates/admin/repos/repos.html:97
+#: rhodecode/templates/admin/users/user_edit_my_account.html:182
+#: rhodecode/templates/admin/users/users.html:110
+#: rhodecode/templates/bookmarks/bookmarks.html:63
+#: rhodecode/templates/branches/branches.html:80
+#: rhodecode/templates/journal/journal.html:214
+#: rhodecode/templates/tags/tags.html:63
+msgid "Data error."
+msgstr ""
+
+#: rhodecode/templates/index_base.html:202
+#: rhodecode/templates/admin/repos/repos.html:98
+#: rhodecode/templates/admin/users/user_edit_my_account.html:183
+#: rhodecode/templates/admin/users/users.html:111
+#: rhodecode/templates/bookmarks/bookmarks.html:64
+#: rhodecode/templates/branches/branches.html:81
+#: rhodecode/templates/journal/journal.html:215
+#: rhodecode/templates/tags/tags.html:64
+#, fuzzy
+msgid "Loading..."
+msgstr "載入中..."
+
+#: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
 msgid "Sign In"
 msgstr "登入"
 
@@ -1016,31 +1450,33 @@ msgstr "登入"
 msgid "Sign In to"
 msgstr "登入"
 
-#: 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:47
-#: rhodecode/templates/admin/users/user_edit_my_account.html:45
-#: rhodecode/templates/base/base.html:15
-#: rhodecode/templates/summary/summary.html:106
+#: rhodecode/templates/admin/users/user_edit.html:50
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
+#: rhodecode/templates/base/base.html:83
+#: rhodecode/templates/summary/summary.html:122
 msgid "Username"
 msgstr "帳號"
 
-#: 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:24
+#: rhodecode/templates/base/base.html:92
 msgid "Password"
 msgstr "密碼"
 
+#: rhodecode/templates/login.html:50
+#, fuzzy
+msgid "Remember me"
+msgstr "成員"
+
 #: rhodecode/templates/login.html:60
 msgid "Forgot your password ?"
 msgstr "忘記您的密碼?"
 
-#: rhodecode/templates/login.html:63
-#: rhodecode/templates/base/base.html:35
+#: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
 msgid "Don't have an account ?"
 msgstr "沒有帳號?"
 
@@ -1064,8 +1500,7 @@ msgstr "重設我的密碼"
 msgid "Password reset link will be send to matching email address"
 msgstr "密碼重設連結已郵寄至您的信箱"
 
-#: rhodecode/templates/register.html:5
-#: rhodecode/templates/register.html:74
+#: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
 msgid "Sign Up"
 msgstr "登入"
 
@@ -1078,24 +1513,24 @@ msgid "Re-enter password"
 msgstr "確認密碼"
 
 #: rhodecode/templates/register.html:47
-#: rhodecode/templates/admin/users/user_add.html:50
-#: rhodecode/templates/admin/users/user_edit.html:74
-#: rhodecode/templates/admin/users/user_edit_my_account.html:63
+#: rhodecode/templates/admin/users/user_add.html:59
+#: rhodecode/templates/admin/users/user_edit.html:86
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
 msgid "First Name"
 msgstr "名"
 
 #: rhodecode/templates/register.html:56
-#: rhodecode/templates/admin/users/user_add.html:59
-#: rhodecode/templates/admin/users/user_edit.html:83
-#: rhodecode/templates/admin/users/user_edit_my_account.html:72
+#: rhodecode/templates/admin/users/user_add.html:68
+#: rhodecode/templates/admin/users/user_edit.html:95
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
 msgid "Last Name"
 msgstr "姓"
 
 #: rhodecode/templates/register.html:65
-#: rhodecode/templates/admin/users/user_add.html:68
-#: rhodecode/templates/admin/users/user_edit.html:92
-#: rhodecode/templates/admin/users/user_edit_my_account.html:81
-#: rhodecode/templates/summary/summary.html:108
+#: rhodecode/templates/admin/users/user_add.html:77
+#: rhodecode/templates/admin/users/user_edit.html:104
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
+#: rhodecode/templates/summary/summary.html:124
 msgid "Email"
 msgstr "電子郵件"
 
@@ -1107,20 +1542,60 @@ msgstr "您的帳號註冊後將會啟用"
 msgid "Your account must wait for activation by administrator"
 msgstr "您的帳號註冊後將等待管理員啟用"
 
-#: rhodecode/templates/repo_switcher_list.html:14
+#: rhodecode/templates/repo_switcher_list.html:11
+#: rhodecode/templates/admin/repos/repo_add_base.html:65
+#: rhodecode/templates/admin/repos/repo_edit.html:85
+#: rhodecode/templates/settings/repo_settings.html:76
 msgid "Private repository"
 msgstr "私有的版本庫"
 
-#: rhodecode/templates/repo_switcher_list.html:19
+#: rhodecode/templates/repo_switcher_list.html:16
 msgid "Public repository"
 msgstr "公開的版本庫"
 
+#: rhodecode/templates/switch_to_list.html:3
+#: rhodecode/templates/branches/branches.html:14
+msgid "branches"
+msgstr "分支"
+
+#: rhodecode/templates/switch_to_list.html:10
+#: rhodecode/templates/branches/branches_data.html:57
+msgid "There are no branches yet"
+msgstr "沒有任何分支"
+
+#: rhodecode/templates/switch_to_list.html:15
+#: rhodecode/templates/shortlog/shortlog_data.html:10
+#: rhodecode/templates/tags/tags.html:15
+msgid "tags"
+msgstr "標籤"
+
+#: rhodecode/templates/switch_to_list.html:22
+#: rhodecode/templates/tags/tags_data.html:33
+msgid "There are no tags yet"
+msgstr "沒有任何標籤"
+
+#: rhodecode/templates/switch_to_list.html:28
+#: rhodecode/templates/bookmarks/bookmarks.html:15
+msgid "bookmarks"
+msgstr ""
+
+#: rhodecode/templates/switch_to_list.html:35
+#: rhodecode/templates/bookmarks/bookmarks_data.html:32
+#, fuzzy
+msgid "There are no bookmarks yet"
+msgstr "尚未有任何 fork"
+
 #: rhodecode/templates/admin/admin.html:5
 #: rhodecode/templates/admin/admin.html:9
 msgid "Admin journal"
 msgstr "管理員日誌"
 
 #: rhodecode/templates/admin/admin_log.html:6
+#: rhodecode/templates/admin/repos/repos.html:74
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
+#: rhodecode/templates/journal/journal.html:61
+#: rhodecode/templates/journal/journal.html:62
 msgid "Action"
 msgstr "動作"
 
@@ -1129,6 +1604,11 @@ msgid "Repository"
 msgstr "版本庫"
 
 #: rhodecode/templates/admin/admin_log.html:8
+#: rhodecode/templates/bookmarks/bookmarks.html:37
+#: rhodecode/templates/bookmarks/bookmarks_data.html:7
+#: rhodecode/templates/branches/branches.html:52
+#: rhodecode/templates/tags/tags.html:37
+#: rhodecode/templates/tags/tags_data.html:7
 msgid "Date"
 msgstr "時間"
 
@@ -1136,7 +1616,7 @@ msgstr "時間"
 msgid "From IP"
 msgstr "來源IP"
 
-#: rhodecode/templates/admin/admin_log.html:52
+#: rhodecode/templates/admin/admin_log.html:53
 msgid "No actions yet"
 msgstr ""
 
@@ -1213,23 +1693,66 @@ msgid "E-mail Attribute"
 msgstr "電子郵件屬性"
 
 #: rhodecode/templates/admin/ldap/ldap.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:141
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
 #: rhodecode/templates/admin/settings/hooks.html:73
-#: rhodecode/templates/admin/users/user_edit.html:117
-#: rhodecode/templates/admin/users/user_edit.html:142
-#: rhodecode/templates/admin/users/user_edit_my_account.html:89
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:263
+#: rhodecode/templates/admin/users/user_edit.html:129
+#: rhodecode/templates/admin/users/user_edit.html:174
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:135
+#: rhodecode/templates/settings/repo_settings.html:93
 msgid "Save"
 msgstr "儲存"
 
+#: rhodecode/templates/admin/notifications/notifications.html:5
+#: rhodecode/templates/admin/notifications/notifications.html:9
+msgid "My Notifications"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:29
+msgid "All"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:30
+#, fuzzy
+msgid "Comments"
+msgstr "遞交"
+
+#: rhodecode/templates/admin/notifications/notifications.html:31
+#: rhodecode/templates/base/base.html:254
+#: rhodecode/templates/base/base.html:256
+msgid "Pull requests"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications.html:35
+msgid "Mark all read"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/notifications_data.html:39
+msgid "No notifications here yet"
+msgstr ""
+
+#: rhodecode/templates/admin/notifications/show_notification.html:5
+#: rhodecode/templates/admin/notifications/show_notification.html:11
+#, fuzzy
+msgid "Show notification"
+msgstr "險是註釋"
+
+#: rhodecode/templates/admin/notifications/show_notification.html:9
+#, fuzzy
+msgid "Notifications"
+msgstr "位置"
+
 #: rhodecode/templates/admin/permissions/permissions.html:5
 msgid "Permissions administration"
 msgstr "權限管理員"
 
 #: rhodecode/templates/admin/permissions/permissions.html:11
-#: rhodecode/templates/admin/repos/repo_edit.html:109
-#: rhodecode/templates/admin/users/user_edit.html:127
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:248
-#: rhodecode/templates/settings/repo_settings.html:58
+#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
+#: rhodecode/templates/admin/users/user_edit.html:139
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:100
+#: rhodecode/templates/settings/repo_settings.html:86
 msgid "Permissions"
 msgstr "權限"
 
@@ -1246,7 +1769,10 @@ msgid "Repository permission"
 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"
+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 ""
 
 #: rhodecode/templates/admin/permissions/permissions.html:50
@@ -1262,6 +1788,12 @@ msgid "Repository creation"
 msgstr "版本庫建立"
 
 #: rhodecode/templates/admin/permissions/permissions.html:71
+#, fuzzy
+msgid "Repository forking"
+msgstr "版本庫建立"
+
+#: rhodecode/templates/admin/permissions/permissions.html:78
+#: rhodecode/templates/admin/repos/repo_edit.html:241
 msgid "set"
 msgstr "設定"
 
@@ -1272,7 +1804,6 @@ msgstr "新增版本庫"
 
 #: rhodecode/templates/admin/repos/repo_add.html:11
 #: rhodecode/templates/admin/repos/repo_edit.html:11
-#: rhodecode/templates/admin/repos/repos.html:10
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
 msgid "Repositories"
 msgstr "版本庫"
@@ -1282,30 +1813,72 @@ msgid "add new"
 msgstr "新增"
 
 #: rhodecode/templates/admin/repos/repo_add_base.html:20
-#: rhodecode/templates/summary/summary.html:80
-#: rhodecode/templates/summary/summary.html:82
+#: rhodecode/templates/summary/summary.html:95
+#: rhodecode/templates/summary/summary.html:96
 msgid "Clone from"
 msgstr "複製由"
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:28
-#: rhodecode/templates/admin/repos/repo_edit.html:48
+#: rhodecode/templates/admin/repos/repo_add_base.html:24
+#: rhodecode/templates/admin/repos/repo_edit.html:44
+#: rhodecode/templates/settings/repo_settings.html:43
+msgid "Optional http[s] url from which repository should be cloned."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:29
+#: rhodecode/templates/admin/repos/repo_edit.html:49
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:4
+#: rhodecode/templates/forks/fork.html:50
+#: rhodecode/templates/settings/repo_settings.html:48
 msgid "Repository group"
 msgstr "版本庫群組"
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:36
-#: rhodecode/templates/admin/repos/repo_edit.html:56
+#: rhodecode/templates/admin/repos/repo_add_base.html:33
+#: rhodecode/templates/forks/fork.html:54
+msgid "Optionaly select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:38
+#: rhodecode/templates/admin/repos/repo_edit.html:58
 msgid "Type"
 msgstr "類型"
 
-#: rhodecode/templates/admin/repos/repo_add_base.html:52
-#: rhodecode/templates/admin/repos/repo_edit.html:73
-#: rhodecode/templates/settings/repo_fork.html:48
-#: rhodecode/templates/settings/repo_settings.html:49
-msgid "Private"
-msgstr "私有"
-
-#: rhodecode/templates/admin/repos/repo_add_base.html:59
+#: rhodecode/templates/admin/repos/repo_add_base.html:42
+#, fuzzy
+msgid "Type of repository to create."
+msgstr "版本庫建立"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:47
+#: rhodecode/templates/admin/repos/repo_edit.html:66
+#: rhodecode/templates/forks/fork.html:41
+#: rhodecode/templates/settings/repo_settings.html:57
+#, fuzzy
+msgid "Landing revision"
+msgstr "下一個修訂"
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:51
+#: rhodecode/templates/admin/repos/repo_edit.html:70
+#: rhodecode/templates/forks/fork.html:45
+#: rhodecode/templates/settings/repo_settings.html:61
+msgid "Default revision for files page, downloads, whoosh and readme"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:60
+#: 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."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:69
+#: 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 ""
+
+#: rhodecode/templates/admin/repos/repo_add_base.html:73
 msgid "add"
 msgstr "新增"
 
@@ -1319,183 +1892,276 @@ msgstr "編輯版本庫"
 
 #: rhodecode/templates/admin/repos/repo_edit.html:13
 #: rhodecode/templates/admin/users/user_edit.html:13
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
+#: rhodecode/templates/admin/users/user_edit.html:224
+#: rhodecode/templates/admin/users/user_edit.html:226
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:13
-#: rhodecode/templates/files/files_annotate.html:49
-#: rhodecode/templates/files/files_source.html:20
+#: rhodecode/templates/files/files_source.html:44
+#: rhodecode/templates/journal/journal.html:81
 msgid "edit"
 msgstr "編輯"
 
 #: rhodecode/templates/admin/repos/repo_edit.html:40
+#: rhodecode/templates/settings/repo_settings.html:39
 msgid "Clone uri"
 msgstr "複製URL"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:81
+#: rhodecode/templates/admin/repos/repo_edit.html:53
+#: rhodecode/templates/settings/repo_settings.html:52
+msgid "Optional select a group to put this repository into."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:94
 msgid "Enable statistics"
 msgstr "啟用統計"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:89
+#: rhodecode/templates/admin/repos/repo_edit.html:98
+msgid "Enable statistics window on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:103
 msgid "Enable downloads"
 msgstr "啟用下載"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:127
+#: rhodecode/templates/admin/repos/repo_edit.html:107
+msgid "Enable download menu on summary page."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:112
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
+#, fuzzy
+msgid "Enable locking"
+msgstr "啟用"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:116
+msgid "Enable lock-by-pulling on repository."
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:126
+#, fuzzy
+msgid "Change owner of this repository."
+msgstr "修改於版本庫 %s"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:142
+#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
+#: rhodecode/templates/admin/settings/settings.html:113
+#: rhodecode/templates/admin/settings/settings.html:168
+#: rhodecode/templates/admin/settings/settings.html:258
+#: rhodecode/templates/admin/users/user_edit.html:130
+#: rhodecode/templates/admin/users/user_edit.html:175
+#: rhodecode/templates/admin/users/user_edit.html:278
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:136
+#: rhodecode/templates/files/files_add.html:82
+#: rhodecode/templates/files/files_edit.html:68
+#: rhodecode/templates/pullrequests/pullrequest.html:124
+#: rhodecode/templates/settings/repo_settings.html:94
+msgid "Reset"
+msgstr "重設"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:152
 msgid "Administration"
 msgstr "管理者"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:130
+#: rhodecode/templates/admin/repos/repo_edit.html:155
 msgid "Statistics"
 msgstr "統計"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Reset current statistics"
 msgstr "重設目前的統計"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:134
+#: rhodecode/templates/admin/repos/repo_edit.html:159
 msgid "Confirm to remove current statistics"
 msgstr "確認移除目前的統計"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:137
+#: rhodecode/templates/admin/repos/repo_edit.html:162
 msgid "Fetched to rev"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:138
-msgid "Percentage of stats gathered"
-msgstr ""
-
-#: rhodecode/templates/admin/repos/repo_edit.html:147
+#: rhodecode/templates/admin/repos/repo_edit.html:163
+msgid "Stats gathered"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:171
 msgid "Remote"
 msgstr "遠端"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:151
+#: rhodecode/templates/admin/repos/repo_edit.html:175
 msgid "Pull changes from remote location"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:151
+#: rhodecode/templates/admin/repos/repo_edit.html:175
 msgid "Confirm to pull changes from remote side"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:162
+#: rhodecode/templates/admin/repos/repo_edit.html:186
 msgid "Cache"
 msgstr "快取"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Invalidate repository cache"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit.html:166
+#: rhodecode/templates/admin/repos/repo_edit.html:190
 msgid "Confirm to invalidate repository cache"
 msgstr "確認廢止版本庫快取"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:177
+#: rhodecode/templates/admin/repos/repo_edit.html:195
+#: rhodecode/templates/base/base.html:318
+#: rhodecode/templates/base/base.html:320
+#: rhodecode/templates/base/base.html:322
+msgid "Public journal"
+msgstr "公開日誌"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:201
 msgid "Remove from public journal"
 msgstr "從公開日誌移除"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:179
+#: rhodecode/templates/admin/repos/repo_edit.html:203
 msgid "Add to public journal"
 msgstr "新增至公開日誌"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:185
+#: rhodecode/templates/admin/repos/repo_edit.html:208
+msgid ""
+"All actions made on this repository will be accessible to everyone in "
+"public journal"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:215
+#, fuzzy
+msgid "Locking"
+msgstr "解鎖"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+msgid "Unlock locked repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:220
+#, fuzzy
+msgid "Confirm to unlock repository"
+msgstr "確認移除這個版本庫"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+msgid "lock repo"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:223
+#, fuzzy
+msgid "Confirm to lock repository"
+msgstr "確認移除這個版本庫"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:224
+#, fuzzy
+msgid "Repository is not locked"
+msgstr "個版本庫"
+
+#: rhodecode/templates/admin/repos/repo_edit.html:229
+msgid "Force locking on repository. Works only when anonymous access is disabled"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:236
+msgid "Set as fork of"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:245
+msgid "Manually set this repository as a fork of another from the list"
+msgstr ""
+
+#: rhodecode/templates/admin/repos/repo_edit.html:251
+#: rhodecode/templates/changeset/changeset_file_comment.html:26
 msgid "Delete"
 msgstr "移除"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
+#: rhodecode/templates/admin/repos/repo_edit.html:255
 msgid "Remove this repository"
 msgstr "移除版本庫"
 
-#: rhodecode/templates/admin/repos/repo_edit.html:189
-#: rhodecode/templates/admin/repos/repos.html:79
+#: rhodecode/templates/admin/repos/repo_edit.html:255
+#: rhodecode/templates/journal/journal.html:84
 msgid "Confirm to delete this repository"
 msgstr "確認移除這個版本庫"
 
+#: rhodecode/templates/admin/repos/repo_edit.html:259
+msgid ""
+"This repository will be renamed in a special way in order to be "
+"unaccesible for RhodeCode and VCS systems.\n"
+"                         If you need fully delete it from filesystem "
+"please do it manually"
+msgstr ""
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:3
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
 msgid "none"
 msgstr "無"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:4
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
 msgid "read"
 msgstr "讀"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:5
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
 msgid "write"
 msgstr "寫"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:6
-#: rhodecode/templates/admin/users/users.html:38
-#: rhodecode/templates/base/base.html:296
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
+#: rhodecode/templates/admin/users/users.html:85
+#: rhodecode/templates/base/base.html:217
 msgid "admin"
 msgstr "管理員"
 
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:7
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
 msgid "member"
 msgstr "成員"
 
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:16
+#: rhodecode/templates/data_table/_dt_elements.html:67
+#: rhodecode/templates/journal/journal.html:132
+#: rhodecode/templates/summary/summary.html:76
+msgid "private repository"
+msgstr "私有版本庫"
+
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:19
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:28
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
+#, fuzzy
+msgid "default"
+msgstr "刪除"
+
 #: rhodecode/templates/admin/repos/repo_edit_perms.html:33
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:53
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:58
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
 msgid "revoke"
 msgstr ""
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:75
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:83
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
 msgid "Add another member"
 msgstr "新增另ㄧ位成員"
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:89
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:97
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
 msgid "Failed to remove user"
 msgstr "移除使用者失敗"
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:104
+#: rhodecode/templates/admin/repos/repo_edit_perms.html:112
+#: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
 msgid "Failed to remove users group"
 msgstr "移除使用者群組失敗"
 
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:205
-msgid "Group"
-msgstr "群組"
-
-#: rhodecode/templates/admin/repos/repo_edit_perms.html:206
-#: rhodecode/templates/admin/users_groups/users_groups.html:33
-msgid "members"
-msgstr "成員"
-
 #: rhodecode/templates/admin/repos/repos.html:5
 msgid "Repositories administration"
 msgstr "版本庫管理員"
 
-#: rhodecode/templates/admin/repos/repos.html:34
-#: rhodecode/templates/summary/summary.html:100
-msgid "Contact"
-msgstr "聯絡方式"
-
-#: rhodecode/templates/admin/repos/repos.html:35
-#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
-#: rhodecode/templates/admin/users/user_edit_my_account.html:119
-#: rhodecode/templates/admin/users/users.html:40
-#: rhodecode/templates/admin/users_groups/users_groups.html:35
-msgid "action"
-msgstr "動作"
-
-#: rhodecode/templates/admin/repos/repos.html:51
-#: rhodecode/templates/admin/users/user_edit_my_account.html:134
-#: rhodecode/templates/admin/users/user_edit_my_account.html:148
-msgid "private"
-msgstr "私有"
-
-#: rhodecode/templates/admin/repos/repos.html:53
-#: rhodecode/templates/admin/repos/repos.html:59
-#: rhodecode/templates/admin/users/user_edit_my_account.html:136
-#: rhodecode/templates/admin/users/user_edit_my_account.html:142
-#: rhodecode/templates/summary/summary.html:68
-msgid "public"
-msgstr "公開"
-
-#: rhodecode/templates/admin/repos/repos.html:79
-#: rhodecode/templates/admin/users/users.html:55
-msgid "delete"
-msgstr "刪除"
-
 #: rhodecode/templates/admin/repos_groups/repos_groups.html:8
 msgid "Groups"
 msgstr "群組"
 
-#: rhodecode/templates/admin/repos_groups/repos_groups.html:13
+#: rhodecode/templates/admin/repos_groups/repos_groups.html:12
 msgid "with"
 msgstr ""
 
@@ -1518,10 +2184,10 @@ msgid "Group parent"
 msgstr "父群組"
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
-#: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
-#: rhodecode/templates/admin/users/user_add.html:85
+#: rhodecode/templates/admin/users/user_add.html:94
 #: rhodecode/templates/admin/users_groups/users_group_add.html:49
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:90
+#: rhodecode/templates/pullrequests/pullrequest_show.html:113
 msgid "save"
 msgstr "儲存"
 
@@ -1533,6 +2199,12 @@ msgstr "編輯版本庫群組"
 msgid "edit repos group"
 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"
+msgstr ""
+
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
 msgid "Repositories groups administration"
 msgstr "版本庫群組管理員"
@@ -1542,11 +2214,27 @@ msgid "ADD NEW GROUP"
 msgstr "新增群組"
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
-msgid "Number of repositories"
+#, fuzzy
+msgid "Number of toplevel repositories"
 msgstr "版本庫數量"
 
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
+#: rhodecode/templates/admin/users/users.html:87
+#: rhodecode/templates/admin/users_groups/users_groups.html:35
+msgid "action"
+msgstr "動作"
+
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
-msgid "Confirm to delete this group"
+#: rhodecode/templates/admin/users/user_edit.html:255
+#: rhodecode/templates/admin/users_groups/users_groups.html:44
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#: rhodecode/templates/data_table/_dt_elements.html:103
+msgid "delete"
+msgstr "刪除"
+
+#: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
+#, fuzzy, python-format
+msgid "Confirm to delete this group: %s"
 msgstr "確認刪除這個群組"
 
 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
@@ -1560,7 +2248,6 @@ msgstr "設定管理員"
 
 #: rhodecode/templates/admin/settings/hooks.html:9
 #: rhodecode/templates/admin/settings/settings.html:9
-#: rhodecode/templates/settings/repo_settings.html:5
 #: rhodecode/templates/settings/repo_settings.html:13
 msgid "Settings"
 msgstr "設定"
@@ -1590,119 +2277,205 @@ msgid "rescan option"
 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."
+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 "如果版本庫已從檔案系統中刪除,但是資料還留在資料庫,請勾選這個項目清理資料庫中舊的資料"
 
 #: rhodecode/templates/admin/settings/settings.html:39
 msgid "destroy old data"
 msgstr "移除舊資料"
 
-#: rhodecode/templates/admin/settings/settings.html:45
+#: rhodecode/templates/admin/settings/settings.html:41
+msgid ""
+"Rescan repositories location for new repositories. Also deletes obsolete "
+"if `destroy` flag is checked "
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:46
 msgid "Rescan repositories"
 msgstr "重新掃描版本庫"
 
-#: rhodecode/templates/admin/settings/settings.html:51
+#: rhodecode/templates/admin/settings/settings.html:52
 msgid "Whoosh indexing"
 msgstr "Whoosh 索引"
 
-#: rhodecode/templates/admin/settings/settings.html:59
+#: rhodecode/templates/admin/settings/settings.html:60
 msgid "index build option"
 msgstr "索引選項"
 
-#: rhodecode/templates/admin/settings/settings.html:64
+#: rhodecode/templates/admin/settings/settings.html:65
 msgid "build from scratch"
 msgstr "重頭建立索引"
 
-#: rhodecode/templates/admin/settings/settings.html:70
+#: rhodecode/templates/admin/settings/settings.html:71
 msgid "Reindex"
 msgstr "重新索引"
 
-#: rhodecode/templates/admin/settings/settings.html:76
+#: rhodecode/templates/admin/settings/settings.html:77
 msgid "Global application settings"
 msgstr "全域設定"
 
-#: rhodecode/templates/admin/settings/settings.html:85
+#: rhodecode/templates/admin/settings/settings.html:86
 msgid "Application name"
 msgstr "應用名稱"
 
-#: rhodecode/templates/admin/settings/settings.html:94
+#: rhodecode/templates/admin/settings/settings.html:95
 msgid "Realm text"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:103
+#: rhodecode/templates/admin/settings/settings.html:104
 msgid "GA code"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:111
-#: rhodecode/templates/admin/settings/settings.html:177
-msgid "Save settings"
-msgstr "儲存設定"
-
 #: rhodecode/templates/admin/settings/settings.html:112
-#: rhodecode/templates/admin/settings/settings.html:178
-#: rhodecode/templates/admin/users/user_edit.html:118
-#: rhodecode/templates/admin/users/user_edit.html:143
-#: rhodecode/templates/admin/users/user_edit_my_account.html:90
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:264
-#: rhodecode/templates/files/files_edit.html:50
-msgid "Reset"
-msgstr "重設"
-
-#: rhodecode/templates/admin/settings/settings.html:118
-msgid "Mercurial settings"
-msgstr "Mercurial 設定"
-
-#: rhodecode/templates/admin/settings/settings.html:127
+#: rhodecode/templates/admin/settings/settings.html:167
+#: rhodecode/templates/admin/settings/settings.html:257
+msgid "Save settings"
+msgstr "儲存設定"
+
+#: rhodecode/templates/admin/settings/settings.html:119
+#, fuzzy
+msgid "Visualisation settings"
+msgstr "全域設定"
+
+#: rhodecode/templates/admin/settings/settings.html:128
+#, fuzzy
+msgid "Icons"
+msgstr "選項"
+
+#: rhodecode/templates/admin/settings/settings.html:133
+msgid "Show public repo icon on repositories"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:137
+#, fuzzy
+msgid "Show private repo icon on repositories"
+msgstr "私有版本庫"
+
+#: rhodecode/templates/admin/settings/settings.html:144
+#, fuzzy
+msgid "Meta-Tagging"
+msgstr "設定"
+
+#: rhodecode/templates/admin/settings/settings.html:149
+msgid "Stylify recognised metatags:"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:176
+#, fuzzy
+msgid "VCS settings"
+msgstr "設定"
+
+#: rhodecode/templates/admin/settings/settings.html:185
 msgid "Web"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:132
-msgid "require ssl for pushing"
+#: rhodecode/templates/admin/settings/settings.html:190
+#, fuzzy
+msgid "require ssl for vcs operations"
 msgstr "推送時要求使用SSL"
 
-#: rhodecode/templates/admin/settings/settings.html:139
+#: rhodecode/templates/admin/settings/settings.html:192
+msgid ""
+"RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
+"will return HTTP Error 406: Not Acceptable"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:198
 msgid "Hooks"
 msgstr ""
 
-#: rhodecode/templates/admin/settings/settings.html:142
+#: rhodecode/templates/admin/settings/settings.html:203
+msgid "Update repository after push (hg update)"
+msgstr "push後更新版本庫 (hg update)"
+
+#: rhodecode/templates/admin/settings/settings.html:207
+msgid "Show repository size after push"
+msgstr "push 後顯示版本庫大小"
+
+#: rhodecode/templates/admin/settings/settings.html:211
+msgid "Log user push commands"
+msgstr "紀錄使用者推送命令"
+
+#: rhodecode/templates/admin/settings/settings.html:215
+msgid "Log user pull commands"
+msgstr "紀錄使用者抓取命令"
+
+#: rhodecode/templates/admin/settings/settings.html:219
 msgid "advanced setup"
 msgstr "進階設定"
 
-#: rhodecode/templates/admin/settings/settings.html:147
-msgid "Update repository after push (hg update)"
-msgstr "push後更新版本庫 (hg update)"
-
-#: rhodecode/templates/admin/settings/settings.html:151
-msgid "Show repository size after push"
-msgstr "push 後顯示版本庫大小"
-
-#: rhodecode/templates/admin/settings/settings.html:155
-msgid "Log user push commands"
-msgstr "紀錄使用者推送命令"
-
-#: rhodecode/templates/admin/settings/settings.html:159
-msgid "Log user pull commands"
-msgstr "紀錄使用者抓取命令"
-
-#: rhodecode/templates/admin/settings/settings.html:166
+#: rhodecode/templates/admin/settings/settings.html:224
+#, fuzzy
+msgid "Mercurial Extensions"
+msgstr "Mercurial 版本庫"
+
+#: rhodecode/templates/admin/settings/settings.html:229
+msgid "largefiles extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:233
+msgid "hgsubversion extensions"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:235
+msgid ""
+"Requires hgsubversion library installed. Allows clonning from svn remote "
+"locations"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:245
 msgid "Repositories location"
 msgstr "版本庫路徑"
 
-#: rhodecode/templates/admin/settings/settings.html:171
-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."
+#: rhodecode/templates/admin/settings/settings.html:250
+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 "這是一個關鍵的設定,如果您確定要修改這個設定,請重新啟動應用程式以套用設定"
 
-#: rhodecode/templates/admin/settings/settings.html:172
+#: rhodecode/templates/admin/settings/settings.html:251
 msgid "unlock"
 msgstr "解鎖"
 
+#: rhodecode/templates/admin/settings/settings.html:252
+msgid ""
+"Location where repositories are stored. After changing this value a "
+"restart, and rescan is required"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:272
+msgid "Test Email"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:280
+#, fuzzy
+msgid "Email to"
+msgstr "電子郵件"
+
+#: rhodecode/templates/admin/settings/settings.html:288
+#, fuzzy
+msgid "Send"
+msgstr "秒"
+
+#: rhodecode/templates/admin/settings/settings.html:294
+msgid "System Info and Packages"
+msgstr ""
+
+#: rhodecode/templates/admin/settings/settings.html:297
+#, fuzzy
+msgid "show"
+msgstr "顯示"
+
 #: rhodecode/templates/admin/users/user_add.html:5
 msgid "Add user"
 msgstr "新增使用者"
 
 #: rhodecode/templates/admin/users/user_add.html:10
 #: rhodecode/templates/admin/users/user_edit.html:11
-#: rhodecode/templates/admin/users/users.html:9
 msgid "Users"
 msgstr "使用者"
 
@@ -1710,8 +2483,13 @@ msgstr "使用者"
 msgid "add new user"
 msgstr "新增使用者"
 
-#: rhodecode/templates/admin/users/user_add.html:77
-#: rhodecode/templates/admin/users/user_edit.html:101
+#: rhodecode/templates/admin/users/user_add.html:50
+#, fuzzy
+msgid "Password confirmation"
+msgstr "密碼不相符"
+
+#: rhodecode/templates/admin/users/user_add.html:86
+#: rhodecode/templates/admin/users/user_edit.html:113
 #: rhodecode/templates/admin/users_groups/users_group_add.html:41
 #: rhodecode/templates/admin/users_groups/users_group_edit.html:42
 msgid "Active"
@@ -1721,36 +2499,101 @@ msgstr "啟用"
 msgid "Edit user"
 msgstr "編輯使用者"
 
-#: rhodecode/templates/admin/users/user_edit.html:33
-#: rhodecode/templates/admin/users/user_edit_my_account.html:32
+#: rhodecode/templates/admin/users/user_edit.html:34
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
 msgid "Change your avatar at"
 msgstr "修改您的頭像於"
 
-#: rhodecode/templates/admin/users/user_edit.html:34
-#: rhodecode/templates/admin/users/user_edit_my_account.html:33
+#: rhodecode/templates/admin/users/user_edit.html:35
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
 msgid "Using"
 msgstr "使用中"
 
-#: rhodecode/templates/admin/users/user_edit.html:40
-#: rhodecode/templates/admin/users/user_edit_my_account.html:39
+#: rhodecode/templates/admin/users/user_edit.html:43
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
 msgid "API key"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:56
+#: rhodecode/templates/admin/users/user_edit.html:59
 msgid "LDAP DN"
 msgstr ""
 
-#: rhodecode/templates/admin/users/user_edit.html:65
-#: rhodecode/templates/admin/users/user_edit_my_account.html:54
+#: rhodecode/templates/admin/users/user_edit.html:68
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
 msgid "New password"
 msgstr "新密碼"
 
-#: rhodecode/templates/admin/users/user_edit.html:135
-#: rhodecode/templates/admin/users_groups/users_group_edit.html:256
+#: rhodecode/templates/admin/users/user_edit.html:77
+#: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
+msgid "New password confirmation"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit.html:147
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:108
+#, fuzzy
+msgid "Inherit default permissions"
+msgstr "預設權限"
+
+#: rhodecode/templates/admin/users/user_edit.html:152
+#: 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 ""
+
+#: rhodecode/templates/admin/users/user_edit.html:158
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:119
 msgid "Create repositories"
 msgstr "建立版本庫"
 
+#: rhodecode/templates/admin/users/user_edit.html:166
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:127
+#, fuzzy
+msgid "Fork repositories"
+msgstr "個版本庫"
+
+#: rhodecode/templates/admin/users/user_edit.html:186
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
+#, fuzzy
+msgid "Nothing here yet"
+msgstr "尚未有任何變更"
+
+#: rhodecode/templates/admin/users/user_edit.html:193
+#: rhodecode/templates/admin/users/user_edit_my_account.html:60
+#: rhodecode/templates/admin/users/user_edit_my_account.html:194
+#, fuzzy
+msgid "Permission"
+msgstr "權限"
+
+#: rhodecode/templates/admin/users/user_edit.html:194
+#, fuzzy
+msgid "Edit Permission"
+msgstr "版本庫權限"
+
+#: rhodecode/templates/admin/users/user_edit.html:243
+#, fuzzy
+msgid "Email addresses"
+msgstr "郵件位址"
+
+#: rhodecode/templates/admin/users/user_edit.html:256
+#, fuzzy, python-format
+msgid "Confirm to delete this email: %s"
+msgstr "確認刪除這個使用者"
+
+#: rhodecode/templates/admin/users/user_edit.html:270
+#, fuzzy
+msgid "New email address"
+msgstr "郵件位址"
+
+#: rhodecode/templates/admin/users/user_edit.html:277
+#, fuzzy
+msgid "Add"
+msgstr "新增"
+
 #: rhodecode/templates/admin/users/user_edit_my_account.html:5
+#: rhodecode/templates/base/base.html:124
 msgid "My account"
 msgstr "我的帳號"
 
@@ -1758,26 +2601,79 @@ msgstr "我的帳號"
 msgid "My Account"
 msgstr "我的帳號"
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:101
-msgid "My repositories"
-msgstr "我的版本庫"
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:107
-msgid "ADD REPOSITORY"
-msgstr "新增版本庫"
-
-#: rhodecode/templates/admin/users/user_edit_my_account.html:118
-#: rhodecode/templates/branches/branches_data.html:7
-#: rhodecode/templates/shortlog/shortlog_data.html:8
-#: rhodecode/templates/tags/tags_data.html:7
-msgid "revision"
+#: rhodecode/templates/admin/users/user_edit_my_account.html:35
+#, fuzzy
+msgid "My permissions"
+msgstr "權限"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:38
+#: rhodecode/templates/journal/journal.html:41
+#, fuzzy
+msgid "My repos"
+msgstr "空的版本庫"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:41
+#, fuzzy
+msgid "My pull requests"
+msgstr "建立使用者 %s"
+
+#: rhodecode/templates/admin/users/user_edit_my_account.html:45
+#, fuzzy
+msgid "Add repo"
+msgstr "新增"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
+msgid "Opened by me"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
+#, python-format
+msgid "Pull request #%s opened on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
+#, fuzzy
+msgid "Confirm to delete this pull request"
+msgstr "確認移除這個版本庫"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
+msgid "I participate in"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
+#, python-format
+msgid "Pull request #%s opened by %s on %s"
+msgstr ""
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
+#: rhodecode/templates/bookmarks/bookmarks.html:40
+#: rhodecode/templates/bookmarks/bookmarks_data.html:9
+#: rhodecode/templates/branches/branches.html:55
+#: rhodecode/templates/journal/journal.html:60
+#: rhodecode/templates/tags/tags.html:40
+#: rhodecode/templates/tags/tags_data.html:9
+msgid "Revision"
 msgstr "修訂"
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:157
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
+#: rhodecode/templates/journal/journal.html:81
+msgid "private"
+msgstr "私有"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:7
+#, fuzzy, python-format
+msgid "Confirm to delete this repository: %s"
+msgstr "確認移除這個版本庫"
+
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
+#: rhodecode/templates/journal/journal.html:94
 msgid "No repositories yet"
 msgstr "沒有任何版本庫"
 
-#: rhodecode/templates/admin/users/user_edit_my_account.html:159
+#: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
+#: rhodecode/templates/journal/journal.html:96
 msgid "create one now"
 msgstr ""
 
@@ -1785,42 +2681,42 @@ msgstr ""
 msgid "Users administration"
 msgstr "使用者管理員"
 
+#: rhodecode/templates/admin/users/users.html:9
+#: rhodecode/templates/base/base.html:223
+msgid "users"
+msgstr "使用者"
+
 #: rhodecode/templates/admin/users/users.html:23
 msgid "ADD NEW USER"
 msgstr "新增使用者"
 
-#: rhodecode/templates/admin/users/users.html:33
+#: rhodecode/templates/admin/users/users.html:77
 msgid "username"
 msgstr "使用者名稱"
 
-#: rhodecode/templates/admin/users/users.html:34
-#: rhodecode/templates/branches/branches_data.html:5
-#: rhodecode/templates/tags/tags_data.html:5
-msgid "name"
-msgstr "名字"
-
-#: rhodecode/templates/admin/users/users.html:35
+#: rhodecode/templates/admin/users/users.html:80
+#, fuzzy
+msgid "firstname"
+msgstr "名"
+
+#: rhodecode/templates/admin/users/users.html:81
 msgid "lastname"
 msgstr "姓"
 
-#: rhodecode/templates/admin/users/users.html:36
+#: rhodecode/templates/admin/users/users.html:82
 msgid "last login"
 msgstr "最後登入"
 
-#: rhodecode/templates/admin/users/users.html:37
+#: rhodecode/templates/admin/users/users.html:84
 #: rhodecode/templates/admin/users_groups/users_groups.html:34
 msgid "active"
 msgstr "啟用"
 
-#: rhodecode/templates/admin/users/users.html:39
-#: rhodecode/templates/base/base.html:305
+#: rhodecode/templates/admin/users/users.html:86
+#: rhodecode/templates/base/base.html:226
 msgid "ldap"
 msgstr ""
 
-#: rhodecode/templates/admin/users/users.html:56
-msgid "Confirm to delete this user"
-msgstr "確認刪除這個使用者"
-
 #: rhodecode/templates/admin/users_groups/users_group_add.html:5
 msgid "Add users group"
 msgstr "新增使用者群組"
@@ -1862,6 +2758,11 @@ msgstr "啟用的成員"
 msgid "Add all elements"
 msgstr "新增索有元素"
 
+#: rhodecode/templates/admin/users_groups/users_group_edit.html:146
+#, fuzzy
+msgid "Group members"
+msgstr "選擇群組成員"
+
 #: rhodecode/templates/admin/users_groups/users_groups.html:5
 msgid "Users groups administration"
 msgstr "使用者群組管理員"
@@ -1874,399 +2775,633 @@ msgstr "建立新的使用者群組"
 msgid "group name"
 msgstr "群組名稱"
 
-#: rhodecode/templates/base/base.html:32
+#: rhodecode/templates/admin/users_groups/users_groups.html:33
+#: rhodecode/templates/base/root.html:46
+msgid "members"
+msgstr "成員"
+
+#: rhodecode/templates/admin/users_groups/users_groups.html:45
+#, fuzzy, python-format
+msgid "Confirm to delete this users group: %s"
+msgstr "確認刪除這個群組"
+
+#: rhodecode/templates/base/base.html:41
+msgid "Submit a bug"
+msgstr "回報錯誤"
+
+#: rhodecode/templates/base/base.html:77
+msgid "Login to your account"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:100
 msgid "Forgot password ?"
 msgstr "忘記密碼?"
 
-#: rhodecode/templates/base/base.html:57
-#: rhodecode/templates/base/base.html:338
-#: rhodecode/templates/base/base.html:340
-#: rhodecode/templates/base/base.html:342
+#: rhodecode/templates/base/base.html:107
+#, fuzzy
+msgid "Log In"
+msgstr "登入"
+
+#: rhodecode/templates/base/base.html:118
+msgid "Inbox"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:122
+#: rhodecode/templates/base/base.html:300
+#: rhodecode/templates/base/base.html:302
+#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/bookmarks/bookmarks.html:11
+#: rhodecode/templates/branches/branches.html:10
+#: rhodecode/templates/changelog/changelog.html:10
+#: rhodecode/templates/changeset/changeset.html:10
+#: rhodecode/templates/changeset/changeset_range.html:9
+#: rhodecode/templates/compare/compare_diff.html:9
+#: rhodecode/templates/files/file_diff.html:8
+#: rhodecode/templates/files/files.html:8
+#: 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/pullrequests/pullrequest.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show.html:8
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
+#: rhodecode/templates/settings/repo_settings.html:9
+#: rhodecode/templates/shortlog/shortlog.html:10
+#: rhodecode/templates/summary/summary.html:8
+#: rhodecode/templates/tags/tags.html:11
 msgid "Home"
 msgstr "首頁"
 
-#: rhodecode/templates/base/base.html:61
-#: rhodecode/templates/base/base.html:347
-#: rhodecode/templates/base/base.html:349
-#: rhodecode/templates/base/base.html:351
+#: rhodecode/templates/base/base.html:123
+#: rhodecode/templates/base/base.html:309
+#: rhodecode/templates/base/base.html:311
+#: rhodecode/templates/base/base.html:313
 #: rhodecode/templates/journal/journal.html:4
-#: rhodecode/templates/journal/journal.html:17
+#: rhodecode/templates/journal/journal.html:21
 #: rhodecode/templates/journal/public_journal.html:4
 msgid "Journal"
 msgstr "日誌"
 
-#: rhodecode/templates/base/base.html:66
-msgid "Login"
-msgstr "登入"
-
-#: rhodecode/templates/base/base.html:68
+#: rhodecode/templates/base/base.html:125
 msgid "Log Out"
 msgstr "登出"
 
-#: rhodecode/templates/base/base.html:107
-msgid "Submit a bug"
-msgstr "回報錯誤"
-
-#: rhodecode/templates/base/base.html:141
+#: rhodecode/templates/base/base.html:144
 msgid "Switch repository"
 msgstr "切換版本庫"
 
-#: rhodecode/templates/base/base.html:143
+#: rhodecode/templates/base/base.html:146
 msgid "Products"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:149
+#: rhodecode/templates/base/base.html:152
+#: rhodecode/templates/base/base.html:182
 msgid "loading..."
 msgstr "載入中..."
 
-#: rhodecode/templates/base/base.html:234
-#: rhodecode/templates/base/base.html:236
-#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:158
+#: rhodecode/templates/base/base.html:160
+#: rhodecode/templates/base/base.html:162
+#: rhodecode/templates/data_table/_dt_elements.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:17
+#: rhodecode/templates/data_table/_dt_elements.html:19
+msgid "Summary"
+msgstr "概況"
+
+#: rhodecode/templates/base/base.html:166
+#: rhodecode/templates/base/base.html:168
+#: rhodecode/templates/base/base.html:170
+#: rhodecode/templates/changelog/changelog.html:15
+#: rhodecode/templates/data_table/_dt_elements.html:23
+#: rhodecode/templates/data_table/_dt_elements.html:25
+#: rhodecode/templates/data_table/_dt_elements.html:27
+msgid "Changelog"
+msgstr "修改紀錄"
+
+#: rhodecode/templates/base/base.html:175
+#: rhodecode/templates/base/base.html:177
+#: rhodecode/templates/base/base.html:179
 msgid "Switch to"
 msgstr "切換至"
 
-#: rhodecode/templates/base/base.html:242
-#: rhodecode/templates/branches/branches.html:13
-msgid "branches"
-msgstr "分支"
-
-#: rhodecode/templates/base/base.html:249
-#: rhodecode/templates/branches/branches_data.html:52
-msgid "There are no branches yet"
-msgstr "沒有任何分支"
-
-#: rhodecode/templates/base/base.html:254
-#: rhodecode/templates/shortlog/shortlog_data.html:10
-#: rhodecode/templates/tags/tags.html:14
-msgid "tags"
-msgstr "標籤"
-
-#: rhodecode/templates/base/base.html:261
-#: rhodecode/templates/tags/tags_data.html:32
-msgid "There are no tags yet"
-msgstr "沒有任何標籤"
-
-#: rhodecode/templates/base/base.html:277
-#: rhodecode/templates/base/base.html:281
-#: rhodecode/templates/files/files_annotate.html:40
-#: rhodecode/templates/files/files_source.html:11
+#: rhodecode/templates/base/base.html:186
+#: rhodecode/templates/base/base.html:188
+#: rhodecode/templates/base/base.html:190
+#: rhodecode/templates/data_table/_dt_elements.html:31
+#: rhodecode/templates/data_table/_dt_elements.html:33
+#: rhodecode/templates/data_table/_dt_elements.html:35
+msgid "Files"
+msgstr "檔案"
+
+#: rhodecode/templates/base/base.html:195
+#: rhodecode/templates/base/base.html:199
 msgid "Options"
 msgstr "選項"
 
-#: rhodecode/templates/base/base.html:286
-#: rhodecode/templates/base/base.html:288
-#: rhodecode/templates/base/base.html:306
+#: rhodecode/templates/base/base.html:204
+#: rhodecode/templates/base/base.html:206
+#: rhodecode/templates/base/base.html:227
 msgid "settings"
 msgstr "設定"
 
-#: rhodecode/templates/base/base.html:292
+#: rhodecode/templates/base/base.html:209
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/forks/fork.html:13
+msgid "fork"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:211
+#: rhodecode/templates/changelog/changelog.html:40
+msgid "Open new pull request"
+msgstr ""
+
+#: rhodecode/templates/base/base.html:213
 msgid "search"
 msgstr "搜尋"
 
-#: rhodecode/templates/base/base.html:299
-msgid "journal"
-msgstr "日誌"
-
-#: rhodecode/templates/base/base.html:301
+#: rhodecode/templates/base/base.html:222
 msgid "repositories groups"
 msgstr "版本庫群組"
 
-#: rhodecode/templates/base/base.html:302
-msgid "users"
-msgstr "使用者"
-
-#: rhodecode/templates/base/base.html:303
+#: rhodecode/templates/base/base.html:224
 msgid "users groups"
 msgstr "使用者群組"
 
-#: rhodecode/templates/base/base.html:304
+#: rhodecode/templates/base/base.html:225
 msgid "permissions"
 msgstr "權限"
 
-#: rhodecode/templates/base/base.html:317
-#: rhodecode/templates/base/base.html:319
-#: rhodecode/templates/followers/followers.html:5
+#: rhodecode/templates/base/base.html:238
+#: rhodecode/templates/base/base.html:240
 msgid "Followers"
 msgstr "追蹤者"
 
-#: rhodecode/templates/base/base.html:325
-#: rhodecode/templates/base/base.html:327
-#: rhodecode/templates/forks/forks.html:5
+#: rhodecode/templates/base/base.html:246
+#: rhodecode/templates/base/base.html:248
 msgid "Forks"
 msgstr ""
 
-#: rhodecode/templates/base/base.html:356
-#: rhodecode/templates/base/base.html:358
-#: rhodecode/templates/base/base.html:360
-#: rhodecode/templates/search/search.html:4
-#: rhodecode/templates/search/search.html:24
-#: rhodecode/templates/search/search.html:46
+#: rhodecode/templates/base/base.html:327
+#: rhodecode/templates/base/base.html:329
+#: rhodecode/templates/base/base.html:331
+#: rhodecode/templates/search/search.html:52
 msgid "Search"
 msgstr "搜尋"
 
-#: rhodecode/templates/base/root.html:57
-#: rhodecode/templates/journal/journal.html:48
-#: rhodecode/templates/summary/summary.html:36
+#: rhodecode/templates/base/root.html:42
+#, fuzzy
+msgid "add another comment"
+msgstr "新增另ㄧ位成員"
+
+#: rhodecode/templates/base/root.html:43
+#: rhodecode/templates/journal/journal.html:120
+#: rhodecode/templates/summary/summary.html:57
 msgid "Stop following this repository"
 msgstr "停止追蹤這個版本庫"
 
-#: rhodecode/templates/base/root.html:66
-#: rhodecode/templates/summary/summary.html:40
+#: rhodecode/templates/base/root.html:44
+#: rhodecode/templates/summary/summary.html:61
 msgid "Start following this repository"
 msgstr "開始追蹤這個版本庫"
 
-#: rhodecode/templates/branches/branches_data.html:4
-#: rhodecode/templates/tags/tags_data.html:4
+#: rhodecode/templates/base/root.html:45
+msgid "Group"
+msgstr "群組"
+
+#: rhodecode/templates/base/root.html:47
+msgid "search truncated"
+msgstr ""
+
+#: rhodecode/templates/base/root.html:48
+msgid "no matching files"
+msgstr "無符合的檔案"
+
+#: rhodecode/templates/bookmarks/bookmarks.html:5
+#, python-format
+msgid "%s Bookmarks"
+msgstr ""
+
+#: rhodecode/templates/bookmarks/bookmarks.html:39
+#: rhodecode/templates/bookmarks/bookmarks_data.html:8
+#: rhodecode/templates/branches/branches.html:54
+#: rhodecode/templates/tags/tags.html:39
+#: rhodecode/templates/tags/tags_data.html:8
+#, fuzzy
+msgid "Author"
+msgstr "作者"
+
+#: rhodecode/templates/branches/branches.html:5
+#, fuzzy, python-format
+msgid "%s Branches"
+msgstr "分支"
+
+#: rhodecode/templates/branches/branches.html:29
+#, fuzzy
+msgid "Compare branches"
+msgstr "分支"
+
+#: rhodecode/templates/branches/branches.html:57
+#: rhodecode/templates/compare/compare_diff.html:5
+#: rhodecode/templates/compare/compare_diff.html:13
+#, fuzzy
+msgid "Compare"
+msgstr "比較顯示"
+
+#: rhodecode/templates/branches/branches_data.html:6
+msgid "name"
+msgstr "名字"
+
+#: rhodecode/templates/branches/branches_data.html:7
 msgid "date"
 msgstr "日期"
 
-#: rhodecode/templates/branches/branches_data.html:6
-#: rhodecode/templates/shortlog/shortlog_data.html:7
-#: rhodecode/templates/tags/tags_data.html:6
-msgid "author"
-msgstr "作者"
-
 #: rhodecode/templates/branches/branches_data.html:8
-#: rhodecode/templates/shortlog/shortlog_data.html:11
-#: rhodecode/templates/tags/tags_data.html:8
-msgid "links"
-msgstr "連結"
-
-#: rhodecode/templates/branches/branches_data.html:23
-#: rhodecode/templates/branches/branches_data.html:43
-#: rhodecode/templates/shortlog/shortlog_data.html:39
-#: rhodecode/templates/tags/tags_data.html:24
-msgid "changeset"
-msgstr "修改"
-
-#: rhodecode/templates/branches/branches_data.html:25
-#: rhodecode/templates/branches/branches_data.html:45
-#: rhodecode/templates/files/files.html:12
-#: rhodecode/templates/shortlog/shortlog_data.html:41
-#: rhodecode/templates/summary/summary.html:233
-#: rhodecode/templates/tags/tags_data.html:26
-msgid "files"
-msgstr "檔案"
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "showing "
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:14
-msgid "out of"
+#: rhodecode/templates/shortlog/shortlog_data.html:8
+msgid "author"
+msgstr "作者"
+
+#: rhodecode/templates/branches/branches_data.html:9
+#: rhodecode/templates/shortlog/shortlog_data.html:5
+msgid "revision"
+msgstr "修訂"
+
+#: rhodecode/templates/branches/branches_data.html:10
+#, fuzzy
+msgid "compare"
+msgstr "比較顯示"
+
+#: rhodecode/templates/changelog/changelog.html:6
+#, fuzzy, python-format
+msgid "%s Changelog"
+msgstr "修改紀錄"
+
+#: rhodecode/templates/changelog/changelog.html:15
+#, python-format
+msgid "showing %d out of %d revision"
+msgid_plural "showing %d out of %d revisions"
+msgstr[0] ""
+
+#: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:19
+#, python-format
+msgid "compare fork with %s"
 msgstr ""
 
 #: rhodecode/templates/changelog/changelog.html:37
+#: rhodecode/templates/forks/forks_data.html:21
+msgid "Compare fork"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:46
 msgid "Show"
 msgstr "顯示"
 
-#: rhodecode/templates/changelog/changelog.html:50
-#: rhodecode/templates/changeset/changeset.html:42
-#: rhodecode/templates/summary/summary.html:609
-msgid "commit"
-msgstr "遞交"
-
-#: rhodecode/templates/changelog/changelog.html:63
+#: rhodecode/templates/changelog/changelog.html:72
+#: rhodecode/templates/summary/summary.html:364
+msgid "show more"
+msgstr "顯示更多"
+
+#: rhodecode/templates/changelog/changelog.html:76
 msgid "Affected number of files, click to show more details"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:67
-#: rhodecode/templates/changeset/changeset.html:66
+#: rhodecode/templates/changelog/changelog.html:89
+#: rhodecode/templates/changeset/changeset.html:38
+#: rhodecode/templates/changeset/changeset_file_comment.html:20
+#: rhodecode/templates/changeset/changeset_range.html:46
+#, fuzzy
+msgid "Changeset status"
+msgstr "變更"
+
+#: rhodecode/templates/changelog/changelog.html:92
+msgid "Click to open associated pull request"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:102
+#: rhodecode/templates/changeset/changeset.html:78
+msgid "Parent"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:108
+#: rhodecode/templates/changeset/changeset.html:84
+msgid "No parents"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:113
+#: rhodecode/templates/changeset/changeset.html:88
 msgid "merge"
 msgstr "合併"
 
-#: rhodecode/templates/changelog/changelog.html:72
-#: rhodecode/templates/changeset/changeset.html:72
-msgid "Parent"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:77
-#: rhodecode/templates/changeset/changeset.html:77
-msgid "No parents"
-msgstr ""
-
-#: rhodecode/templates/changelog/changelog.html:82
-#: rhodecode/templates/changeset/changeset.html:80
+#: rhodecode/templates/changelog/changelog.html:116
+#: rhodecode/templates/changeset/changeset.html:91
 #: rhodecode/templates/files/files.html:29
-#: rhodecode/templates/files/files_annotate.html:25
+#: rhodecode/templates/files/files_add.html:33
 #: rhodecode/templates/files/files_edit.html:33
 #: rhodecode/templates/shortlog/shortlog_data.html:9
 msgid "branch"
 msgstr "分支"
 
-#: rhodecode/templates/changelog/changelog.html:86
-#: rhodecode/templates/changeset/changeset.html:83
+#: rhodecode/templates/changelog/changelog.html:122
+msgid "bookmark"
+msgstr ""
+
+#: rhodecode/templates/changelog/changelog.html:128
+#: rhodecode/templates/changeset/changeset.html:96
 msgid "tag"
 msgstr "標籤"
 
-#: rhodecode/templates/changelog/changelog.html:122
+#: rhodecode/templates/changelog/changelog.html:164
 msgid "Show selected changes __S -> __E"
 msgstr ""
 
-#: rhodecode/templates/changelog/changelog.html:172
-#: rhodecode/templates/shortlog/shortlog_data.html:61
+#: rhodecode/templates/changelog/changelog.html:255
 msgid "There are no changes yet"
 msgstr "尚未有任何變更"
 
-#: rhodecode/templates/changelog/changelog_details.html:2
-#: rhodecode/templates/changeset/changeset.html:55
+#: rhodecode/templates/changelog/changelog_details.html:4
+#: rhodecode/templates/changeset/changeset.html:66
 msgid "removed"
 msgstr "移除"
 
-#: rhodecode/templates/changelog/changelog_details.html:3
-#: rhodecode/templates/changeset/changeset.html:56
+#: rhodecode/templates/changelog/changelog_details.html:5
+#: rhodecode/templates/changeset/changeset.html:67
 msgid "changed"
 msgstr "修改"
 
-#: rhodecode/templates/changelog/changelog_details.html:4
-#: rhodecode/templates/changeset/changeset.html:57
+#: rhodecode/templates/changelog/changelog_details.html:6
+#: rhodecode/templates/changeset/changeset.html:68
 msgid "added"
 msgstr "新增"
 
-#: rhodecode/templates/changelog/changelog_details.html:6
-#: rhodecode/templates/changelog/changelog_details.html:7
 #: rhodecode/templates/changelog/changelog_details.html:8
-#: rhodecode/templates/changeset/changeset.html:59
-#: rhodecode/templates/changeset/changeset.html:60
-#: rhodecode/templates/changeset/changeset.html:61
+#: rhodecode/templates/changelog/changelog_details.html:9
+#: rhodecode/templates/changelog/changelog_details.html:10
+#: rhodecode/templates/changeset/changeset.html:70
+#: rhodecode/templates/changeset/changeset.html:71
+#: rhodecode/templates/changeset/changeset.html:72
 #, python-format
 msgid "affected %s files"
 msgstr ""
 
 #: rhodecode/templates/changeset/changeset.html:6
+#, fuzzy, python-format
+msgid "%s Changeset"
+msgstr "沒有修改"
+
 #: rhodecode/templates/changeset/changeset.html:14
-#: rhodecode/templates/changeset/changeset.html:31
 msgid "Changeset"
 msgstr ""
 
-#: rhodecode/templates/changeset/changeset.html:32
-#: rhodecode/templates/changeset/changeset.html:121
-#: rhodecode/templates/changeset/changeset_range.html:78
-#: rhodecode/templates/files/file_diff.html:32
-#: rhodecode/templates/files/file_diff.html:42
+#: rhodecode/templates/changeset/changeset.html:43
+#: rhodecode/templates/changeset/diff_block.html:20
 msgid "raw diff"
 msgstr "原始差異"
 
-#: rhodecode/templates/changeset/changeset.html:34
-#: rhodecode/templates/changeset/changeset.html:123
-#: rhodecode/templates/changeset/changeset_range.html:80
-#: rhodecode/templates/files/file_diff.html:34
+#: rhodecode/templates/changeset/changeset.html:44
+#: rhodecode/templates/changeset/diff_block.html:21
 msgid "download diff"
 msgstr "下載差異"
 
-#: rhodecode/templates/changeset/changeset.html:90
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
+#, fuzzy, python-format
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "遞交"
+
+#: rhodecode/templates/changeset/changeset.html:48
+#: rhodecode/templates/changeset/changeset_file_comment.html:82
 #, python-format
-msgid "%s files affected with %s additions and %s deletions."
-msgstr ""
-
-#: rhodecode/templates/changeset/changeset.html:101
-msgid "Changeset was too big and was cut off..."
+msgid "(%d inline)"
+msgid_plural "(%d inline)"
+msgstr[0] ""
+
+#: rhodecode/templates/changeset/changeset.html:103
+#, python-format
+msgid "%s files affected with %s insertions and %s deletions:"
 msgstr ""
 
 #: rhodecode/templates/changeset/changeset.html:119
-#: rhodecode/templates/changeset/changeset_range.html:76
-#: rhodecode/templates/files/file_diff.html:30
+msgid "Changeset was too big and was cut off..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:42
+msgid "Submitting..."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:45
+msgid "Commenting on line {1}."
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:46
+#: rhodecode/templates/changeset/changeset_file_comment.html:121
+#, python-format
+msgid "Comments parsed using %s syntax with %s support."
+msgstr ""
+
+#: 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 ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:59
+#: rhodecode/templates/changeset/changeset_file_comment.html:138
+#, fuzzy
+msgid "Comment"
+msgstr "遞交"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:60
+#: rhodecode/templates/changeset/changeset_file_comment.html:71
+msgid "Hide"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+#, fuzzy
+msgid "You need to be logged in to comment."
+msgstr "您必須登入後才能瀏覽這個頁面"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:67
+msgid "Login now"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:118
+msgid "Leave a comment"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+msgid "Check this to change current status of code-review for this changeset"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:124
+#, fuzzy
+msgid "change status"
+msgstr "變更"
+
+#: rhodecode/templates/changeset/changeset_file_comment.html:140
+msgid "Comment and close"
+msgstr ""
+
+#: rhodecode/templates/changeset/changeset_range.html:5
+#, fuzzy, python-format
+msgid "%s Changesets"
+msgstr "變更"
+
+#: rhodecode/templates/changeset/changeset_range.html:29
+#: rhodecode/templates/compare/compare_diff.html:29
+msgid "Compare View"
+msgstr "比較顯示"
+
+#: rhodecode/templates/changeset/changeset_range.html:54
+#: rhodecode/templates/compare/compare_diff.html:41
+#: rhodecode/templates/pullrequests/pullrequest_show.html:69
+msgid "Files affected"
+msgstr ""
+
+#: rhodecode/templates/changeset/diff_block.html:19
 msgid "diff"
 msgstr "差異"
 
-#: rhodecode/templates/changeset/changeset.html:132
-#: rhodecode/templates/changeset/changeset_range.html:89
-msgid "No changes in this file"
-msgstr "這個檔案沒有任何變更"
-
-#: rhodecode/templates/changeset/changeset_range.html:30
-msgid "Compare View"
-msgstr "比較顯示"
-
-#: rhodecode/templates/changeset/changeset_range.html:52
-msgid "Files affected"
-msgstr ""
-
-#: rhodecode/templates/errors/error_document.html:44
+#: rhodecode/templates/changeset/diff_block.html:27
+#, fuzzy
+msgid "show inline comments"
+msgstr "文件內容"
+
+#: rhodecode/templates/compare/compare_cs.html:5
+#, fuzzy
+msgid "No changesets"
+msgstr "尚未有任何變更"
+
+#: rhodecode/templates/compare/compare_diff.html:37
+#, fuzzy
+msgid "Outgoing changesets"
+msgstr "尚未有任何變更"
+
+#: rhodecode/templates/data_table/_dt_elements.html:39
+#: rhodecode/templates/data_table/_dt_elements.html:41
+#: rhodecode/templates/data_table/_dt_elements.html:43
+msgid "Fork"
+msgstr "分支"
+
+#: rhodecode/templates/data_table/_dt_elements.html:60
+#: rhodecode/templates/journal/journal.html:126
+#: rhodecode/templates/summary/summary.html:68
+msgid "Mercurial repository"
+msgstr "Mercurial 版本庫"
+
+#: rhodecode/templates/data_table/_dt_elements.html:62
+#: rhodecode/templates/journal/journal.html:128
+#: rhodecode/templates/summary/summary.html:71
+msgid "Git repository"
+msgstr "Git 版本庫"
+
+#: rhodecode/templates/data_table/_dt_elements.html:69
+#: rhodecode/templates/journal/journal.html:134
+#: rhodecode/templates/summary/summary.html:78
+msgid "public repository"
+msgstr "公開版本庫"
+
+#: rhodecode/templates/data_table/_dt_elements.html:80
+#: rhodecode/templates/summary/summary.html:87
+#: rhodecode/templates/summary/summary.html:88
+msgid "Fork of"
+msgstr ""
+
+#: rhodecode/templates/data_table/_dt_elements.html:92
+msgid "No changesets yet"
+msgstr "尚未有任何變更"
+
+#: rhodecode/templates/data_table/_dt_elements.html:104
+#, fuzzy, python-format
+msgid "Confirm to delete this user: %s"
+msgstr "確認刪除這個使用者"
+
+#: rhodecode/templates/email_templates/main.html:8
+msgid "This is an notification from RhodeCode."
+msgstr ""
+
+#: rhodecode/templates/errors/error_document.html:46
 #, python-format
 msgid "You will be redirected to %s in %s seconds"
 msgstr ""
 
 #: rhodecode/templates/files/file_diff.html:4
+#, fuzzy, python-format
+msgid "%s File diff"
+msgstr "檔案差異"
+
 #: rhodecode/templates/files/file_diff.html:12
 msgid "File diff"
 msgstr "檔案差異"
 
-#: rhodecode/templates/files/file_diff.html:42
-msgid "Diff is to big to display"
-msgstr ""
-
-#: rhodecode/templates/files/files.html:37
-#: rhodecode/templates/files/files_annotate.html:31
+#: rhodecode/templates/files/files.html:4
+#: rhodecode/templates/files/files.html:72
+#, fuzzy, python-format
+msgid "%s files"
+msgstr "檔案"
+
+#: rhodecode/templates/files/files.html:12
+#: rhodecode/templates/summary/summary.html:340
+msgid "files"
+msgstr "檔案"
+
+#: rhodecode/templates/files/files_add.html:4
+#: rhodecode/templates/files/files_edit.html:4
+#, fuzzy, python-format
+msgid "%s Edit file"
+msgstr "編輯檔案"
+
+#: rhodecode/templates/files/files_add.html:19
+#, fuzzy
+msgid "add file"
+msgstr "編輯檔案"
+
+#: rhodecode/templates/files/files_add.html:40
+#, fuzzy
+msgid "Add new file"
+msgstr "新增使用者"
+
+#: rhodecode/templates/files/files_add.html:45
+#, fuzzy
+msgid "File Name"
+msgstr "檔案名稱"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:58
+#, fuzzy
+msgid "or"
+msgstr "時"
+
+#: rhodecode/templates/files/files_add.html:49
+#: rhodecode/templates/files/files_add.html:54
+#, fuzzy
+msgid "Upload file"
+msgstr "編輯檔案"
+
+#: rhodecode/templates/files/files_add.html:58
+#, fuzzy
+msgid "Create new file"
+msgstr "建立使用者 %s"
+
+#: rhodecode/templates/files/files_add.html:63
 #: rhodecode/templates/files/files_edit.html:39
+#: rhodecode/templates/files/files_ypjax.html:3
 msgid "Location"
 msgstr "位置"
 
-#: rhodecode/templates/files/files.html:46
-msgid "Go back"
-msgstr ""
-
-#: rhodecode/templates/files/files.html:47
-msgid "No files at given path"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:4
-msgid "File annotate"
-msgstr "檔案註釋"
-
-#: rhodecode/templates/files/files_annotate.html:12
-msgid "annotate"
-msgstr "註釋"
-
-#: rhodecode/templates/files/files_annotate.html:33
-#: rhodecode/templates/files/files_browser.html:160
-#: rhodecode/templates/files/files_source.html:2
-msgid "Revision"
-msgstr "修訂"
-
-#: rhodecode/templates/files/files_annotate.html:36
-#: rhodecode/templates/files/files_browser.html:158
-#: rhodecode/templates/files/files_source.html:7
-msgid "Size"
-msgstr "大小"
-
-#: rhodecode/templates/files/files_annotate.html:38
-#: rhodecode/templates/files/files_browser.html:159
-#: rhodecode/templates/files/files_source.html:9
-msgid "Mimetype"
-msgstr ""
-
-#: rhodecode/templates/files/files_annotate.html:41
-msgid "show source"
-msgstr "顯示原始碼"
-
-#: rhodecode/templates/files/files_annotate.html:43
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:14
-#: rhodecode/templates/files/files_source.html:51
-msgid "show as raw"
-msgstr "顯示原始文件"
-
-#: rhodecode/templates/files/files_annotate.html:45
-#: rhodecode/templates/files/files_source.html:16
-msgid "download as raw"
-msgstr "下載原始文件"
-
-#: rhodecode/templates/files/files_annotate.html:54
-#: rhodecode/templates/files/files_source.html:25
-msgid "History"
-msgstr "歷史"
-
-#: rhodecode/templates/files/files_annotate.html:73
-#: rhodecode/templates/files/files_source.html:46
-#, python-format
-msgid "Binary file (%s)"
-msgstr "二進位檔 (%s)"
-
-#: rhodecode/templates/files/files_annotate.html:78
-#: rhodecode/templates/files/files_source.html:51
-msgid "File is too big to display"
-msgstr "顯示的檔案太大"
+#: rhodecode/templates/files/files_add.html:67
+msgid "use / to separate directories"
+msgstr ""
+
+#: rhodecode/templates/files/files_add.html:77
+#: rhodecode/templates/files/files_edit.html:63
+#: rhodecode/templates/shortlog/shortlog_data.html:6
+msgid "commit message"
+msgstr "遞交資訊"
+
+#: rhodecode/templates/files/files_add.html:81
+#: rhodecode/templates/files/files_edit.html:67
+msgid "Commit changes"
+msgstr "遞交修改"
 
 #: rhodecode/templates/files/files_browser.html:13
 msgid "view"
@@ -2288,59 +3423,170 @@ msgstr ""
 msgid "search file list"
 msgstr "搜尋檔案列表"
 
-#: rhodecode/templates/files/files_browser.html:32
+#: rhodecode/templates/files/files_browser.html:31
+#: rhodecode/templates/shortlog/shortlog_data.html:65
+#, fuzzy
+msgid "add new file"
+msgstr "新增使用者"
+
+#: rhodecode/templates/files/files_browser.html:35
 msgid "Loading file list..."
 msgstr "載入檔案列表..."
 
-#: rhodecode/templates/files/files_browser.html:111
-msgid "search truncated"
-msgstr ""
-
-#: rhodecode/templates/files/files_browser.html:122
-msgid "no matching files"
-msgstr "無符合的檔案"
-
-#: rhodecode/templates/files/files_browser.html:161
+#: rhodecode/templates/files/files_browser.html:48
+msgid "Size"
+msgstr "大小"
+
+#: rhodecode/templates/files/files_browser.html:49
+msgid "Mimetype"
+msgstr ""
+
+#: rhodecode/templates/files/files_browser.html:50
+#, fuzzy
+msgid "Last Revision"
+msgstr "下一個修訂"
+
+#: rhodecode/templates/files/files_browser.html:51
 msgid "Last modified"
 msgstr "最後修改"
 
-#: rhodecode/templates/files/files_browser.html:162
+#: rhodecode/templates/files/files_browser.html:52
 msgid "Last commiter"
 msgstr "最後的遞交者"
 
-#: rhodecode/templates/files/files_edit.html:4
-msgid "Edit file"
-msgstr "編輯檔案"
-
 #: rhodecode/templates/files/files_edit.html:19
 msgid "edit file"
 msgstr "編輯檔案"
 
-#: rhodecode/templates/files/files_edit.html:45
-#: rhodecode/templates/shortlog/shortlog_data.html:5
-msgid "commit message"
-msgstr "遞交資訊"
+#: rhodecode/templates/files/files_edit.html:49
+#: rhodecode/templates/files/files_source.html:38
+msgid "show annotation"
+msgstr "險是註釋"
+
+#: rhodecode/templates/files/files_edit.html:50
+#: rhodecode/templates/files/files_source.html:40
+#: rhodecode/templates/files/files_source.html:68
+msgid "show as raw"
+msgstr "顯示原始文件"
 
 #: rhodecode/templates/files/files_edit.html:51
-msgid "Commit changes"
-msgstr "遞交修改"
-
-#: rhodecode/templates/files/files_source.html:12
-msgid "show annotation"
+#: rhodecode/templates/files/files_source.html:41
+msgid "download as raw"
+msgstr "下載原始文件"
+
+#: rhodecode/templates/files/files_edit.html:54
+#, fuzzy
+msgid "source"
+msgstr "顯示原始碼"
+
+#: rhodecode/templates/files/files_edit.html:59
+#, fuzzy
+msgid "Editing file"
+msgstr "編輯檔案"
+
+#: rhodecode/templates/files/files_source.html:2
+msgid "History"
+msgstr "歷史"
+
+#: rhodecode/templates/files/files_source.html:9
+#, fuzzy
+msgid "diff to revision"
+msgstr "下一個修訂"
+
+#: rhodecode/templates/files/files_source.html:10
+#, fuzzy
+msgid "show at revision"
+msgstr "下一個修訂"
+
+#: rhodecode/templates/files/files_source.html:14
+#, fuzzy, python-format
+msgid "%s author"
+msgid_plural "%s authors"
+msgstr[0] "作者"
+
+#: rhodecode/templates/files/files_source.html:36
+msgid "show source"
+msgstr "顯示原始碼"
+
+#: rhodecode/templates/files/files_source.html:59
+#, python-format
+msgid "Binary file (%s)"
+msgstr "二進位檔 (%s)"
+
+#: rhodecode/templates/files/files_source.html:68
+msgid "File is too big to display"
+msgstr "顯示的檔案太大"
+
+#: rhodecode/templates/files/files_source.html:124
+msgid "Selection link"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:5
+#, fuzzy
+msgid "annotation"
 msgstr "險是註釋"
 
-#: rhodecode/templates/files/files_source.html:153
-msgid "Selection link"
-msgstr ""
+#: rhodecode/templates/files/files_ypjax.html:15
+msgid "Go back"
+msgstr ""
+
+#: rhodecode/templates/files/files_ypjax.html:16
+msgid "No files at given path"
+msgstr ""
+
+#: rhodecode/templates/followers/followers.html:5
+#, fuzzy, python-format
+msgid "%s Followers"
+msgstr "追蹤者"
 
 #: rhodecode/templates/followers/followers.html:13
 msgid "followers"
 msgstr "追蹤者"
 
 #: rhodecode/templates/followers/followers_data.html:12
-msgid "Started following"
+#, fuzzy
+msgid "Started following -"
 msgstr "開始追蹤"
 
+#: rhodecode/templates/forks/fork.html:5
+#, fuzzy, python-format
+msgid "%s Fork"
+msgstr "分支"
+
+#: rhodecode/templates/forks/fork.html:31
+msgid "Fork name"
+msgstr "分支名稱"
+
+#: rhodecode/templates/forks/fork.html:68
+msgid "Private"
+msgstr "私有"
+
+#: rhodecode/templates/forks/fork.html:77
+#, fuzzy
+msgid "Copy permissions"
+msgstr "權限"
+
+#: rhodecode/templates/forks/fork.html:81
+msgid "Copy permissions from forked repository"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:86
+msgid "Update after clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:90
+msgid "Checkout source after making a clone"
+msgstr ""
+
+#: rhodecode/templates/forks/fork.html:94
+msgid "fork this repository"
+msgstr "fork 這個版本庫"
+
+#: rhodecode/templates/forks/forks.html:5
+#, fuzzy, python-format
+msgid "%s Forks"
+msgstr "分支"
+
 #: rhodecode/templates/forks/forks.html:13
 msgid "forks"
 msgstr "分支"
@@ -2349,184 +3595,419 @@ msgstr "分支"
 msgid "forked"
 msgstr "已建立分支"
 
-#: rhodecode/templates/forks/forks_data.html:34
+#: rhodecode/templates/forks/forks_data.html:38
 msgid "There are no forks yet"
 msgstr "尚未有任何 fork"
 
-#: rhodecode/templates/journal/journal.html:34
-msgid "Following"
-msgstr "已追蹤"
-
-#: rhodecode/templates/journal/journal.html:41
-msgid "following user"
-msgstr "追蹤使用者"
+#: rhodecode/templates/journal/journal.html:13
+#, fuzzy
+msgid "ATOM journal feed"
+msgstr "%s 公開日誌 %s feed"
+
+#: rhodecode/templates/journal/journal.html:14
+#, fuzzy
+msgid "RSS journal feed"
+msgstr "%s 公開日誌 %s feed"
+
+#: rhodecode/templates/journal/journal.html:24
+#: rhodecode/templates/pullrequests/pullrequest.html:27
+msgid "Refresh"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:27
+#: rhodecode/templates/journal/public_journal.html:24
+msgid "RSS feed"
+msgstr ""
+
+#: rhodecode/templates/journal/journal.html:30
+#: rhodecode/templates/journal/public_journal.html:27
+msgid "ATOM feed"
+msgstr ""
 
 #: rhodecode/templates/journal/journal.html:41
+#, fuzzy
+msgid "Watched"
+msgstr "快取"
+
+#: rhodecode/templates/journal/journal.html:46
+#, fuzzy
+msgid "ADD"
+msgstr "新增"
+
+#: rhodecode/templates/journal/journal.html:114
+msgid "following user"
+msgstr "追蹤使用者"
+
+#: rhodecode/templates/journal/journal.html:114
 msgid "user"
 msgstr "使用者"
 
-#: rhodecode/templates/journal/journal.html:65
+#: rhodecode/templates/journal/journal.html:147
 msgid "You are not following any users or repositories"
 msgstr "您尚未追蹤任何使用者或版本庫"
 
-#: rhodecode/templates/journal/journal_data.html:46
+#: rhodecode/templates/journal/journal_data.html:47
 msgid "No entries yet"
 msgstr ""
 
-#: rhodecode/templates/journal/public_journal.html:17
+#: rhodecode/templates/journal/public_journal.html:13
+#, fuzzy
+msgid "ATOM public journal feed"
+msgstr "%s 公開日誌 %s feed"
+
+#: rhodecode/templates/journal/public_journal.html:14
+#, fuzzy
+msgid "RSS public journal feed"
+msgstr "%s 公開日誌 %s feed"
+
+#: rhodecode/templates/journal/public_journal.html:21
 msgid "Public Journal"
 msgstr "開放日誌"
 
-#: rhodecode/templates/search/search.html:7
-#: rhodecode/templates/search/search.html:26
-msgid "in repository: "
+#: rhodecode/templates/pullrequests/pullrequest.html:4
+#: rhodecode/templates/pullrequests/pullrequest.html:12
+msgid "New pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:28
+msgid "refresh overview"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:66
+#, fuzzy
+msgid "Detailed compare view"
+msgstr "比較顯示"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:70
+#: rhodecode/templates/pullrequests/pullrequest_show.html:82
+msgid "Pull request reviewers"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:79
+#: rhodecode/templates/pullrequests/pullrequest_show.html:94
+#, fuzzy
+msgid "owner"
+msgstr "擁有者"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:91
+#: rhodecode/templates/pullrequests/pullrequest_show.html:109
+msgid "Add reviewer to this pull request."
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest.html:97
+#, fuzzy
+msgid "Create new pull request"
+msgstr "建立使用者 %s"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:106
+#: rhodecode/templates/pullrequests/pullrequest_show.html:25
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
+#, fuzzy
+msgid "Title"
+msgstr "寫"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:115
+#, fuzzy
+msgid "description"
+msgstr "描述"
+
+#: rhodecode/templates/pullrequests/pullrequest.html:123
+msgid "Send pull request"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:23
+#, python-format
+msgid "Closed %s"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:31
+#, fuzzy
+msgid "Status"
+msgstr "變更"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:36
+msgid "Pull request status"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:44
+msgid "Still not reviewed by"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:47
+#, python-format
+msgid "%d reviewer"
+msgid_plural "%d reviewers"
+msgstr[0] ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:54
+#, fuzzy
+msgid "Created on"
+msgstr "建立使用者 %s"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:61
+#, fuzzy
+msgid "Compare view"
+msgstr "比較顯示"
+
+#: rhodecode/templates/pullrequests/pullrequest_show.html:65
+#, fuzzy
+msgid "Incoming changesets"
+msgstr "尚未有任何變更"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
+#, fuzzy
+msgid "all pull requests"
+msgstr "建立使用者 %s"
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
+msgid "All pull requests"
+msgstr ""
+
+#: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
+msgid "Closed"
+msgstr ""
+
+#: rhodecode/templates/search/search.html:6
+#, fuzzy, python-format
+msgid "Search \"%s\" in repository: %s"
 msgstr "於版本庫:"
 
-#: rhodecode/templates/search/search.html:9
-#: rhodecode/templates/search/search.html:28
-msgid "in all repositories"
+#: rhodecode/templates/search/search.html:8
+#, fuzzy, python-format
+msgid "Search \"%s\" in all repositories"
 msgstr "於所有的版本庫"
 
-#: rhodecode/templates/search/search.html:42
+#: rhodecode/templates/search/search.html:12
+#: rhodecode/templates/search/search.html:32
+#, fuzzy, python-format
+msgid "Search in repository: %s"
+msgstr "於版本庫:"
+
+#: rhodecode/templates/search/search.html:14
+#: rhodecode/templates/search/search.html:34
+#, fuzzy
+msgid "Search in all repositories"
+msgstr "於所有的版本庫"
+
+#: rhodecode/templates/search/search.html:48
 msgid "Search term"
 msgstr "搜尋關鍵字"
 
-#: rhodecode/templates/search/search.html:54
+#: rhodecode/templates/search/search.html:60
 msgid "Search in"
 msgstr "搜尋範圍"
 
-#: rhodecode/templates/search/search.html:57
+#: rhodecode/templates/search/search.html:63
 msgid "File contents"
 msgstr "文件內容"
 
-#: rhodecode/templates/search/search.html:59
+#: rhodecode/templates/search/search.html:64
+#, fuzzy
+msgid "Commit messages"
+msgstr "遞交資訊"
+
+#: rhodecode/templates/search/search.html:65
 msgid "File names"
 msgstr "檔案名稱"
 
-#: rhodecode/templates/search/search_content.html:20
+#: rhodecode/templates/search/search_commit.html:35
+#: rhodecode/templates/search/search_content.html:21
 #: rhodecode/templates/search/search_path.html:15
 msgid "Permission denied"
 msgstr "權限不足"
 
-#: rhodecode/templates/settings/repo_fork.html:5
-msgid "Fork"
-msgstr "分支"
-
-#: rhodecode/templates/settings/repo_fork.html:31
-msgid "Fork name"
-msgstr "分支名稱"
-
-#: rhodecode/templates/settings/repo_fork.html:55
-msgid "fork this repository"
-msgstr "fork 這個版本庫"
+#: rhodecode/templates/settings/repo_settings.html:5
+#, fuzzy, python-format
+msgid "%s Settings"
+msgstr "設定"
 
 #: rhodecode/templates/shortlog/shortlog.html:5
-#: rhodecode/templates/summary/summary.html:666
-msgid "Shortlog"
+#, fuzzy, python-format
+msgid "%s Shortlog"
 msgstr "簡短紀錄"
 
 #: rhodecode/templates/shortlog/shortlog.html:14
 msgid "shortlog"
 msgstr "簡短紀錄"
 
-#: rhodecode/templates/shortlog/shortlog_data.html:6
+#: rhodecode/templates/shortlog/shortlog_data.html:7
 msgid "age"
 msgstr ""
 
+#: rhodecode/templates/shortlog/shortlog_data.html:18
+#, fuzzy
+msgid "No commit message"
+msgstr "遞交資訊"
+
+#: rhodecode/templates/shortlog/shortlog_data.html:62
+msgid "Add or upload files directly via RhodeCode"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:71
+msgid "Push new repo"
+msgstr ""
+
+#: rhodecode/templates/shortlog/shortlog_data.html:79
+#, fuzzy
+msgid "Existing repository?"
+msgstr "Git 版本庫"
+
+#: rhodecode/templates/summary/summary.html:4
+#, fuzzy, python-format
+msgid "%s Summary"
+msgstr "概況"
+
 #: rhodecode/templates/summary/summary.html:12
 msgid "summary"
 msgstr "概況"
 
-#: rhodecode/templates/summary/summary.html:79
+#: rhodecode/templates/summary/summary.html:20
+#, fuzzy, python-format
+msgid "repo %s ATOM feed"
+msgstr "訂閱 %s atom"
+
+#: rhodecode/templates/summary/summary.html:21
+#, fuzzy, python-format
+msgid "repo %s RSS feed"
+msgstr "訂閱 %s rss"
+
+#: rhodecode/templates/summary/summary.html:49
+#: rhodecode/templates/summary/summary.html:52
+#, fuzzy
+msgid "ATOM"
+msgstr "作者"
+
+#: rhodecode/templates/summary/summary.html:82
+#, fuzzy, python-format
+msgid "Non changable ID %s"
+msgstr "沒有修改"
+
+#: rhodecode/templates/summary/summary.html:87
+msgid "public"
+msgstr "公開"
+
+#: rhodecode/templates/summary/summary.html:95
 msgid "remote clone"
 msgstr "遠端複製"
 
-#: rhodecode/templates/summary/summary.html:121
-msgid "by"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:128
+#: rhodecode/templates/summary/summary.html:116
+msgid "Contact"
+msgstr "聯絡方式"
+
+#: rhodecode/templates/summary/summary.html:130
 msgid "Clone url"
 msgstr "複製連結"
 
-#: rhodecode/templates/summary/summary.html:137
-msgid "Trending source files"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:146
+#: rhodecode/templates/summary/summary.html:133
+#, fuzzy
+msgid "Show by Name"
+msgstr "顯示更多"
+
+#: rhodecode/templates/summary/summary.html:134
+msgid "Show by ID"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:142
+#, fuzzy
+msgid "Trending files"
+msgstr "編輯檔案"
+
+#: rhodecode/templates/summary/summary.html:150
+#: rhodecode/templates/summary/summary.html:166
+#: rhodecode/templates/summary/summary.html:194
+msgid "enable"
+msgstr "啟用"
+
+#: rhodecode/templates/summary/summary.html:158
 msgid "Download"
 msgstr "下載"
 
-#: rhodecode/templates/summary/summary.html:150
+#: rhodecode/templates/summary/summary.html:162
 msgid "There are no downloads yet"
 msgstr "沒有任何下載"
 
-#: rhodecode/templates/summary/summary.html:152
+#: rhodecode/templates/summary/summary.html:164
 msgid "Downloads are disabled for this repository"
 msgstr "這個版本庫的下載已停用"
 
-#: rhodecode/templates/summary/summary.html:154
-#: rhodecode/templates/summary/summary.html:320
-msgid "enable"
-msgstr "啟用"
-
-#: rhodecode/templates/summary/summary.html:162
-#: rhodecode/templates/summary/summary.html:297
+#: rhodecode/templates/summary/summary.html:170
+#, fuzzy
+msgid "Download as zip"
+msgstr "下載原始文件"
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "Check this to download archive with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:173
+msgid "with subrepos"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:186
+msgid "Commit activity by day / author"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:197
+msgid "Stats gathered: "
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:218
+msgid "Shortlog"
+msgstr "簡短紀錄"
+
+#: rhodecode/templates/summary/summary.html:220
+#, fuzzy
+msgid "Quick start"
+msgstr "快速過濾..."
+
+#: rhodecode/templates/summary/summary.html:233
+#, python-format
+msgid "Readme file at revision '%s'"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:236
+msgid "Permalink to this readme"
+msgstr ""
+
+#: rhodecode/templates/summary/summary.html:293
 #, python-format
 msgid "Download %s as %s"
 msgstr "下載 %s 為 %s"
 
-#: rhodecode/templates/summary/summary.html:168
-msgid "Check this to download archive with subrepos"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:168
-msgid "with subrepos"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:176
-msgid "Feeds"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:257
-#: rhodecode/templates/summary/summary.html:684
-#: rhodecode/templates/summary/summary.html:695
-msgid "show more"
-msgstr "顯示更多"
-
-#: rhodecode/templates/summary/summary.html:312
-msgid "Commit activity by day / author"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:324
-msgid "Loaded in"
-msgstr ""
-
-#: rhodecode/templates/summary/summary.html:603
+#: rhodecode/templates/summary/summary.html:650
 msgid "commits"
 msgstr "遞交"
 
-#: rhodecode/templates/summary/summary.html:604
+#: rhodecode/templates/summary/summary.html:651
 msgid "files added"
 msgstr "多個檔案新增"
 
-#: rhodecode/templates/summary/summary.html:605
+#: rhodecode/templates/summary/summary.html:652
 msgid "files changed"
 msgstr "多個檔案修改"
 
-#: rhodecode/templates/summary/summary.html:606
+#: rhodecode/templates/summary/summary.html:653
 msgid "files removed"
 msgstr "移除多個檔案"
 
-#: rhodecode/templates/summary/summary.html:610
+#: rhodecode/templates/summary/summary.html:656
+msgid "commit"
+msgstr "遞交"
+
+#: rhodecode/templates/summary/summary.html:657
 msgid "file added"
 msgstr "檔案新增"
 
-#: rhodecode/templates/summary/summary.html:611
+#: rhodecode/templates/summary/summary.html:658
 msgid "file changed"
 msgstr "檔案修改"
 
-#: rhodecode/templates/summary/summary.html:612
+#: rhodecode/templates/summary/summary.html:659
 msgid "file removed"
 msgstr "移除檔案"
 
+#: rhodecode/templates/tags/tags.html:5
+#, fuzzy, python-format
+msgid "%s Tags"
+msgstr "之前"
+
diff --git a/rhodecode/lib/auth.py b/rhodecode/lib/auth.py
--- a/rhodecode/lib/auth.py
+++ b/rhodecode/lib/auth.py
@@ -35,14 +35,9 @@ from pylons import config, url, request
 from pylons.controllers.util import abort, redirect
 from pylons.i18n.translation import _
 
-from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
+from rhodecode import __platform__, is_windows, is_unix
 from rhodecode.model.meta import Session
 
-if __platform__ in PLATFORM_WIN:
-    from hashlib import sha256
-if __platform__ in PLATFORM_OTHERS:
-    import bcrypt
-
 from rhodecode.lib.utils2 import str2bool, safe_unicode
 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
 from rhodecode.lib.utils import get_repo_slug, get_repos_group_slug
@@ -97,9 +92,11 @@ class RhodeCodeCrypto(object):
 
         :param password: password to hash
         """
-        if __platform__ in PLATFORM_WIN:
+        if is_windows:
+            from hashlib import sha256
             return sha256(str_).hexdigest()
-        elif __platform__ in PLATFORM_OTHERS:
+        elif is_unix:
+            import bcrypt
             return bcrypt.hashpw(str_, bcrypt.gensalt(10))
         else:
             raise Exception('Unknown or unsupported platform %s' \
@@ -115,9 +112,11 @@ class RhodeCodeCrypto(object):
         :param hashed: password in hashed form
         """
 
-        if __platform__ in PLATFORM_WIN:
+        if is_windows:
+            from hashlib import sha256
             return sha256(password).hexdigest() == hashed
-        elif __platform__ in PLATFORM_OTHERS:
+        elif is_unix:
+            import bcrypt
             return bcrypt.hashpw(password, hashed) == hashed
         else:
             raise Exception('Unknown or unsupported platform %s' \
@@ -236,7 +235,7 @@ def authenticate(username, password):
                                           user_attrs):
                     log.info('created new ldap user %s' % username)
 
-                Session.commit()
+                Session().commit()
                 return True
             except (LdapUsernameError, LdapPasswordError,):
                 pass
@@ -263,7 +262,7 @@ def login_container_auth(username):
         return None
 
     user.update_lastlogin()
-    Session.commit()
+    Session().commit()
 
     log.debug('User %s is now logged in by container authentication',
               user.username)
@@ -325,6 +324,7 @@ class  AuthUser(object):
         self.email = ''
         self.is_authenticated = False
         self.admin = False
+        self.inherit_default_permissions = False
         self.permissions = {}
         self._api_key = api_key
         self.propagate_data()
@@ -351,6 +351,7 @@ class  AuthUser(object):
             log.debug('Auth User lookup by USER NAME %s' % self.username)
             dbuser = login_container_auth(self.username)
             if dbuser is not None:
+                log.debug('filling all attributes to object')
                 for k, v in dbuser.get_dict().items():
                     setattr(self, k, v)
                 self.set_authenticated()
@@ -460,8 +461,9 @@ class LoginRequired(object):
         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:
-            log.info('user %s is authenticated and granted access to %s' % (
-                       user.username, loc)
+            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)
             )
             return func(*fargs, **fkwargs)
         else:
@@ -768,7 +770,7 @@ class HasReposGroupPermissionAny(PermsFu
 class HasReposGroupPermissionAll(PermsFunction):
     def __call__(self, group_name=None, check_Location=''):
         self.group_name = group_name
-        return super(HasReposGroupPermissionAny, self).__call__(check_Location)
+        return super(HasReposGroupPermissionAll, self).__call__(check_Location)
 
     def check_permissions(self):
         try:
@@ -805,7 +807,7 @@ class HasPermissionAnyMiddleware(object)
         return self.check_permissions()
 
     def check_permissions(self):
-        log.debug('checking mercurial protocol '
+        log.debug('checking VCS protocol '
                   'permissions %s for user:%s repository:%s', self.user_perms,
                                                 self.username, self.repo_name)
         if self.required_perms.intersection(self.user_perms):
diff --git a/rhodecode/lib/auth_ldap.py b/rhodecode/lib/auth_ldap.py
--- a/rhodecode/lib/auth_ldap.py
+++ b/rhodecode/lib/auth_ldap.py
@@ -27,6 +27,7 @@ import logging
 
 from rhodecode.lib.exceptions import LdapConnectionError, LdapUsernameError, \
     LdapPasswordError
+from rhodecode.lib.utils2 import safe_str
 
 log = logging.getLogger(__name__)
 
@@ -60,15 +61,15 @@ class AuthLdap(object):
         self.LDAP_SERVER_PORT = port
 
         # USE FOR READ ONLY BIND TO LDAP SERVER
-        self.LDAP_BIND_DN = bind_dn
-        self.LDAP_BIND_PASS = bind_pass
+        self.LDAP_BIND_DN = safe_str(bind_dn)
+        self.LDAP_BIND_PASS = safe_str(bind_pass)
 
         self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
                                            self.LDAP_SERVER_ADDRESS,
                                            self.LDAP_SERVER_PORT)
 
-        self.BASE_DN = base_dn
-        self.LDAP_FILTER = ldap_filter
+        self.BASE_DN = safe_str(base_dn)
+        self.LDAP_FILTER = safe_str(ldap_filter)
         self.SEARCH_SCOPE = getattr(ldap, 'SCOPE_%s' % search_scope)
         self.attr_login = attr_login
 
diff --git a/rhodecode/lib/base.py b/rhodecode/lib/base.py
--- a/rhodecode/lib/base.py
+++ b/rhodecode/lib/base.py
@@ -8,6 +8,7 @@ import traceback
 
 from paste.auth.basic import AuthBasicAuthenticator
 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden
+from webob.exc import HTTPClientError
 from paste.httpheaders import WWW_AUTHENTICATE
 
 from pylons import config, tmpl_context as c, request, session, url
@@ -17,19 +18,47 @@ from pylons.templating import render_mak
 
 from rhodecode import __version__, BACKENDS
 
-from rhodecode.lib.utils2 import str2bool, safe_unicode
+from rhodecode.lib.utils2 import str2bool, safe_unicode, AttributeDict,\
+    safe_str
 from rhodecode.lib.auth import AuthUser, get_container_username, authfunc,\
     HasPermissionAnyMiddleware, CookieStoreWrapper
 from rhodecode.lib.utils import get_repo_slug, invalidate_cache
 from rhodecode.model import meta
 
-from rhodecode.model.db import Repository
+from rhodecode.model.db import Repository, RhodeCodeUi, User
 from rhodecode.model.notification import NotificationModel
 from rhodecode.model.scm import ScmModel
+from rhodecode.model.meta import Session
 
 log = logging.getLogger(__name__)
 
 
+def _get_ip_addr(environ):
+    proxy_key = 'HTTP_X_REAL_IP'
+    proxy_key2 = 'HTTP_X_FORWARDED_FOR'
+    def_key = 'REMOTE_ADDR'
+
+    ip = environ.get(proxy_key2)
+    if ip:
+        return ip
+
+    ip = environ.get(proxy_key)
+
+    if ip:
+        return ip
+
+    ip = environ.get(def_key, '0.0.0.0')
+    return ip
+
+
+def _get_access_path(environ):
+    path = environ.get('PATH_INFO')
+    org_req = environ.get('pylons.original_request')
+    if org_req:
+        path = org_req.environ.get('PATH_INFO')
+    return path
+
+
 class BasicAuth(AuthBasicAuthenticator):
 
     def __init__(self, realm, authfunc, auth_http_code=None):
@@ -117,15 +146,65 @@ class BaseVCSController(object):
         return True
 
     def _get_ip_addr(self, environ):
-        proxy_key = 'HTTP_X_REAL_IP'
-        proxy_key2 = 'HTTP_X_FORWARDED_FOR'
-        def_key = 'REMOTE_ADDR'
+        return _get_ip_addr(environ)
+
+    def _check_ssl(self, environ, start_response):
+        """
+        Checks the SSL check flag and returns False if SSL is not present
+        and required True otherwise
+        """
+        org_proto = environ['wsgi._org_proto']
+        #check if we have SSL required  ! if not it's a bad request !
+        require_ssl = str2bool(RhodeCodeUi.get_by_key('push_ssl').ui_value)
+        if require_ssl and org_proto == 'http':
+            log.debug('proto is %s and SSL is required BAD REQUEST !'
+                      % org_proto)
+            return False
+        return True
+
+    def _check_locking_state(self, environ, action, repo, user_id):
+        """
+        Checks locking on this repository, if locking is enabled and lock is
+        present returns a tuple of make_lock, locked, locked_by.
+        make_lock can have 3 states None (do nothing) True, make lock
+        False release lock, This value is later propagated to hooks, which
+        do the locking. Think about this as signals passed to hooks what to do.
+
+        """
+        locked = False  # defines that locked error should be thrown to user
+        make_lock = None
+        repo = Repository.get_by_repo_name(repo)
+        user = User.get(user_id)
 
-        return environ.get(proxy_key2,
-                           environ.get(proxy_key,
-                                       environ.get(def_key, '0.0.0.0')
-                            )
-                        )
+        # this is kind of hacky, but due to how mercurial handles client-server
+        # server see all operation on changeset; bookmarks, phases and
+        # obsolescence marker in different transaction, we don't want to check
+        # locking on those
+        obsolete_call = environ['QUERY_STRING'] in ['cmd=listkeys',]
+        locked_by = repo.locked
+        if repo and repo.enable_locking and not obsolete_call:
+            if action == 'push':
+                #check if it's already locked !, if it is compare users
+                user_id, _date = repo.locked
+                if user.user_id == user_id:
+                    log.debug('Got push from user %s, now unlocking' % (user))
+                    # unlock if we have push from user who locked
+                    make_lock = False
+                else:
+                    # we're not the same user who locked, ban with 423 !
+                    locked = True
+            if action == 'pull':
+                if repo.locked[0] and repo.locked[1]:
+                    locked = True
+                else:
+                    log.debug('Setting lock on repo %s by %s' % (repo, user))
+                    make_lock = True
+
+        else:
+            log.debug('Repository %s do not have locking enabled' % (repo))
+        log.debug('FINAL locking values make_lock:%s,locked:%s,locked_by:%s'
+                  % (make_lock, locked, locked_by))
+        return make_lock, locked, locked_by
 
     def __call__(self, environ, start_response):
         start = time.time()
@@ -145,6 +224,12 @@ class BaseController(WSGIController):
         c.rhodecode_name = config.get('rhodecode_title')
         c.use_gravatar = str2bool(config.get('use_gravatar'))
         c.ga_code = config.get('rhodecode_ga_code')
+        # Visual options
+        c.visual = AttributeDict({})
+        c.visual.show_public_icon = str2bool(config.get('rhodecode_show_public_icon'))
+        c.visual.show_private_icon = str2bool(config.get('rhodecode_show_private_icon'))
+        c.visual.stylify_metatags = str2bool(config.get('rhodecode_stylify_metatags'))
+
         c.repo_name = get_repo_slug(request)
         c.backends = BACKENDS.keys()
         c.unread_notifications = NotificationModel()\
@@ -153,6 +238,7 @@ 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"""
@@ -161,6 +247,7 @@ class BaseController(WSGIController):
         # available in environ['pylons.routes_dict']
         start = time.time()
         try:
+            self.ip_addr = _get_ip_addr(environ)
             # make sure that we update permissions each time we call controller
             api_key = request.GET.get('api_key')
             cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
@@ -174,13 +261,14 @@ class BaseController(WSGIController):
                 self.rhodecode_user.set_authenticated(
                     cookie_store.get('is_authenticated')
                 )
-            log.info('User: %s accessed %s' % (
-                auth_user, safe_unicode(environ.get('PATH_INFO')))
+            log.info('IP: %s User: %s accessed %s' % (
+               self.ip_addr, auth_user, safe_unicode(_get_access_path(environ)))
             )
             return WSGIController.__call__(self, environ, start_response)
         finally:
-            log.info('Request to %s time: %.3fs' % (
-                safe_unicode(environ.get('PATH_INFO')), time.time() - start)
+            log.info('IP: %s Request to %s time: %.3fs' % (
+                _get_ip_addr(environ),
+                safe_unicode(_get_access_path(environ)), time.time() - start)
             )
             meta.Session.remove()
 
@@ -200,7 +288,7 @@ class BaseRepoController(BaseController)
         super(BaseRepoController, self).__before__()
         if c.repo_name:
 
-            c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
+            dbr = c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
             c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
 
             if c.rhodecode_repo is None:
@@ -209,5 +297,7 @@ class BaseRepoController(BaseController)
 
                 redirect(url('home'))
 
-            c.repository_followers = self.scm_model.get_followers(c.repo_name)
-            c.repository_forks = self.scm_model.get_forks(c.repo_name)
+            # some globals counter for menu
+            c.repository_followers = self.scm_model.get_followers(dbr)
+            c.repository_forks = self.scm_model.get_forks(dbr)
+            c.repository_pull_requests = self.scm_model.get_pull_requests(dbr)
diff --git a/rhodecode/lib/celerylib/__init__.py b/rhodecode/lib/celerylib/__init__.py
--- a/rhodecode/lib/celerylib/__init__.py
+++ b/rhodecode/lib/celerylib/__init__.py
@@ -112,7 +112,7 @@ def get_session():
     if CELERY_ON:
         engine = engine_from_config(config, 'sqlalchemy.db1.')
         init_model(engine)
-    sa = meta.Session
+    sa = meta.Session()
     return sa
 
 
diff --git a/rhodecode/lib/celerylib/tasks.py b/rhodecode/lib/celerylib/tasks.py
--- a/rhodecode/lib/celerylib/tasks.py
+++ b/rhodecode/lib/celerylib/tasks.py
@@ -75,7 +75,7 @@ def get_logger(cls):
 @dbsession
 def whoosh_index(repo_location, full_index):
     from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
-    log = whoosh_index.get_logger(whoosh_index)
+    log = get_logger(whoosh_index)
     DBS = get_session()
 
     index_location = config['index_dir']
@@ -367,32 +367,46 @@ def create_repo_fork(form_data, cur_user
     :param cur_user:
     """
     from rhodecode.model.repo import RepoModel
+    from rhodecode.model.user import UserModel
 
     log = get_logger(create_repo_fork)
     DBS = get_session()
 
     base_path = Repository.base_path()
-
-    fork_repo = RepoModel(DBS).create(form_data, cur_user,
-                                      just_db=True, fork=True)
+    cur_user = UserModel(DBS)._get_user(cur_user)
 
-    alias = form_data['repo_type']
-    org_repo_name = form_data['org_path']
     fork_name = form_data['repo_name_full']
+    repo_type = form_data['repo_type']
+    description = form_data['description']
+    owner = cur_user
+    private = form_data['private']
+    clone_uri = form_data.get('clone_uri')
+    repos_group = form_data['repo_group']
+    landing_rev = form_data['landing_rev']
+    copy_fork_permissions = form_data.get('copy_permissions')
+    fork_of = RepoModel(DBS)._get_repo(form_data.get('fork_parent_id'))
+
+    fork_repo = RepoModel(DBS).create_repo(
+        fork_name, repo_type, description, owner, private, clone_uri,
+        repos_group, landing_rev, just_db=True, fork_of=fork_of,
+        copy_fork_permissions=copy_fork_permissions
+    )
+
     update_after_clone = form_data['update_after_clone']
-    source_repo_path = os.path.join(base_path, org_repo_name)
+
+    source_repo_path = os.path.join(base_path, fork_of.repo_name)
     destination_fork_path = os.path.join(base_path, fork_name)
 
     log.info('creating fork of %s as %s', source_repo_path,
              destination_fork_path)
-    backend = get_backend(alias)
+    backend = get_backend(repo_type)
     backend(safe_str(destination_fork_path), create=True,
             src_url=safe_str(source_repo_path),
             update_after_clone=update_after_clone)
     log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
 
     action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
-                   org_repo_name, '', DBS)
+                   fork_of.repo_name, '', DBS)
 
     action_logger(cur_user, 'user_created_fork:%s' % fork_name,
                    fork_name, '', DBS)
diff --git a/rhodecode/lib/cleanup.py b/rhodecode/lib/cleanup.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/cleanup.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+"""
+    package.rhodecode.lib.cleanup
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :created_on: Jul 14, 2012
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski 
+    :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 .
+from __future__ import with_statement
+
+import os
+import sys
+import re
+import shutil
+import logging
+import datetime
+
+from os.path import dirname as dn, join as jn
+from rhodecode.model import init_model
+from rhodecode.lib.utils2 import engine_from_config
+from rhodecode.model.db import RhodeCodeUi
+
+
+#to get the rhodecode import
+sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
+
+from rhodecode.lib.utils import BasePasterCommand, Command, ask_ok,\
+    REMOVED_REPO_PAT, add_cache
+
+log = logging.getLogger(__name__)
+
+
+class CleanupCommand(BasePasterCommand):
+
+    max_args = 1
+    min_args = 1
+
+    usage = "CONFIG_FILE"
+    summary = "Cleanup deleted repos"
+    group_name = "RhodeCode"
+    takes_config_file = -1
+    parser = Command.standard_parser(verbose=True)
+
+    def _parse_older_than(self, val):
+        regex = re.compile(r'((?P\d+?)d)?((?P\d+?)h)?((?P\d+?)m)?((?P\d+?)s)?')
+        parts = regex.match(val)
+        if not parts:
+            return
+        parts = parts.groupdict()
+        time_params = {}
+        for (name, param) in parts.iteritems():
+            if param:
+                time_params[name] = int(param)
+        return datetime.timedelta(**time_params)
+
+    def _extract_date(self, name):
+        """
+        Extract the date part from rm__ pattern of removed repos,
+        and convert it to datetime object
+
+        :param name:
+        """
+        date_part = name[4:19]  # 4:19 since we don't parse milisecods
+        return datetime.datetime.strptime(date_part, '%Y%m%d_%H%M%S')
+
+    def command(self):
+        logging.config.fileConfig(self.path_to_ini_file)
+        from pylons import config
+
+        #get to remove repos !!
+        add_cache(config)
+        engine = engine_from_config(config, 'sqlalchemy.db1.')
+        init_model(engine)
+
+        repos_location = RhodeCodeUi.get_repos_location()
+        to_remove = []
+        for loc in os.listdir(repos_location):
+            if REMOVED_REPO_PAT.match(loc):
+                to_remove.append([loc, self._extract_date(loc)])
+
+        #filter older than (if present)!
+        now = datetime.datetime.now()
+        older_than = self.options.older_than
+        if older_than:
+            to_remove_filtered = []
+            older_than_date = self._parse_older_than(older_than)
+            for name, date_ in to_remove:
+                repo_age = now - date_
+                if repo_age > older_than_date:
+                    to_remove_filtered.append([name, date_])
+
+            to_remove = to_remove_filtered
+            print >> sys.stdout, 'removing [%s] deleted repos older than %s[%s]' \
+                % (len(to_remove), older_than, older_than_date)
+        else:
+            print >> sys.stdout, 'removing all [%s] deleted repos' \
+                % len(to_remove)
+        if self.options.dont_ask or not to_remove:
+            # don't ask just remove !
+            remove = True
+        else:
+            remove = ask_ok('are you sure to remove listed repos \n%s [y/n]?'
+                            % ', \n'.join(['%s removed on %s' % (x[0], x[1])
+                                           for x in to_remove]))
+
+        if remove:
+            for name, date_ in to_remove:
+                print >> sys.stdout, 'removing repository %s' % name
+                shutil.rmtree(os.path.join(repos_location, name))
+        else:
+            print 'nothing done exiting...'
+            sys.exit(0)
+
+    def update_parser(self):
+        self.parser.add_option('--older-than',
+                          action='store',
+                          dest='older_than',
+                          help=(
+                            "only remove repos that have been removed "
+                            "at least given time ago "
+                            "ex. --older-than=30d deletes repositores "
+                            "removed more than 30days ago. Possible options "
+                            "d[ays]/h[ours]/m[inutes]/s[seconds]. OPTIONAL"),
+                          )
+        self.parser.add_option('--dont-ask',
+                               action='store_true',
+                               dest='dont_ask',
+                               help=("Don't ask to remove repos"))
diff --git a/rhodecode/lib/compat.py b/rhodecode/lib/compat.py
--- a/rhodecode/lib/compat.py
+++ b/rhodecode/lib/compat.py
@@ -25,7 +25,7 @@
 # along with this program.  If not, see .
 
 import os
-from rhodecode import __platform__, PLATFORM_WIN
+from rhodecode import __platform__, PLATFORM_WIN, __py_version__
 
 #==============================================================================
 # json
@@ -394,3 +394,192 @@ except ImportError:
             result = [x + [y] for x in result for y in pool]
         for prod in result:
             yield tuple(prod)
+
+
+#==============================================================================
+# BytesIO
+#==============================================================================
+
+try:
+    from io import BytesIO
+except ImportError:
+    from cStringIO import StringIO as BytesIO
+
+
+#==============================================================================
+# deque
+#==============================================================================
+
+if __py_version__ >= (2, 6):
+    from collections import deque
+else:
+    #need to implement our own deque with maxlen
+    class deque(object):
+
+        def __init__(self, iterable=(), maxlen=-1):
+            if not hasattr(self, 'data'):
+                self.left = self.right = 0
+                self.data = {}
+            self.maxlen = maxlen
+            self.extend(iterable)
+
+        def append(self, x):
+            self.data[self.right] = x
+            self.right += 1
+            if self.maxlen != -1 and len(self) > self.maxlen:
+                self.popleft()
+
+        def appendleft(self, x):
+            self.left -= 1
+            self.data[self.left] = x
+            if self.maxlen != -1 and len(self) > self.maxlen:
+                self.pop()
+
+        def pop(self):
+            if self.left == self.right:
+                raise IndexError('cannot pop from empty deque')
+            self.right -= 1
+            elem = self.data[self.right]
+            del self.data[self.right]
+            return elem
+
+        def popleft(self):
+            if self.left == self.right:
+                raise IndexError('cannot pop from empty deque')
+            elem = self.data[self.left]
+            del self.data[self.left]
+            self.left += 1
+            return elem
+
+        def clear(self):
+            self.data.clear()
+            self.left = self.right = 0
+
+        def extend(self, iterable):
+            for elem in iterable:
+                self.append(elem)
+
+        def extendleft(self, iterable):
+            for elem in iterable:
+                self.appendleft(elem)
+
+        def rotate(self, n=1):
+            if self:
+                n %= len(self)
+                for i in xrange(n):
+                    self.appendleft(self.pop())
+
+        def __getitem__(self, i):
+            if i < 0:
+                i += len(self)
+            try:
+                return self.data[i + self.left]
+            except KeyError:
+                raise IndexError
+
+        def __setitem__(self, i, value):
+            if i < 0:
+                i += len(self)
+            try:
+                self.data[i + self.left] = value
+            except KeyError:
+                raise IndexError
+
+        def __delitem__(self, i):
+            size = len(self)
+            if not (-size <= i < size):
+                raise IndexError
+            data = self.data
+            if i < 0:
+                i += size
+            for j in xrange(self.left + i, self.right - 1):
+                data[j] = data[j + 1]
+            self.pop()
+
+        def __len__(self):
+            return self.right - self.left
+
+        def __cmp__(self, other):
+            if type(self) != type(other):
+                return cmp(type(self), type(other))
+            return cmp(list(self), list(other))
+
+        def __repr__(self, _track=[]):
+            if id(self) in _track:
+                return '...'
+            _track.append(id(self))
+            r = 'deque(%r, maxlen=%s)' % (list(self), self.maxlen)
+            _track.remove(id(self))
+            return r
+
+        def __getstate__(self):
+            return (tuple(self),)
+
+        def __setstate__(self, s):
+            self.__init__(s[0])
+
+        def __hash__(self):
+            raise TypeError
+
+        def __copy__(self):
+            return self.__class__(self)
+
+        def __deepcopy__(self, memo={}):
+            from copy import deepcopy
+            result = self.__class__()
+            memo[id(self)] = result
+            result.__init__(deepcopy(tuple(self), memo))
+            return result
+
+
+#==============================================================================
+# threading.Event
+#==============================================================================
+
+if __py_version__ >= (2, 6):
+    from threading import Event
+else:
+    from threading import _Verbose, Condition, Lock
+
+    def Event(*args, **kwargs):
+        return _Event(*args, **kwargs)
+
+    class _Event(_Verbose):
+
+        # After Tim Peters' event class (without is_posted())
+
+        def __init__(self, verbose=None):
+            _Verbose.__init__(self, verbose)
+            self.__cond = Condition(Lock())
+            self.__flag = False
+
+        def isSet(self):
+            return self.__flag
+
+        is_set = isSet
+
+        def set(self):
+            self.__cond.acquire()
+            try:
+                self.__flag = True
+                self.__cond.notify_all()
+            finally:
+                self.__cond.release()
+
+        def clear(self):
+            self.__cond.acquire()
+            try:
+                self.__flag = False
+            finally:
+                self.__cond.release()
+
+        def wait(self, timeout=None):
+            self.__cond.acquire()
+            try:
+                if not self.__flag:
+                    self.__cond.wait(timeout)
+            finally:
+                self.__cond.release()
+
+
+
diff --git a/rhodecode/lib/db_manage.py b/rhodecode/lib/db_manage.py
--- a/rhodecode/lib/db_manage.py
+++ b/rhodecode/lib/db_manage.py
@@ -31,17 +31,20 @@ import logging
 from os.path import dirname as dn, join as jn
 
 from rhodecode import __dbversion__
-from rhodecode.model import meta
 
 from rhodecode.model.user import UserModel
 from rhodecode.lib.utils import ask_ok
 from rhodecode.model import init_model
 from rhodecode.model.db import User, Permission, RhodeCodeUi, \
-    RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup,\
+    RhodeCodeSetting, UserToPerm, DbMigrateVersion, RepoGroup, \
     UserRepoGroupToPerm
 
 from sqlalchemy.engine import create_engine
+from sqlalchemy.schema import MetaData
 from rhodecode.model.repos_group import ReposGroupModel
+#from rhodecode.model import meta
+from rhodecode.model.meta import Session, Base
+
 
 log = logging.getLogger(__name__)
 
@@ -59,25 +62,25 @@ class DbManage(object):
     def init_db(self):
         engine = create_engine(self.dburi, echo=self.log_sql)
         init_model(engine)
-        self.sa = meta.Session
+        self.sa = Session()
 
-    def create_tables(self, override=False):
+    def create_tables(self, override=False, defaults={}):
         """
         Create a auth database
         """
-
+        quiet = defaults.get('quiet')
         log.info("Any existing database is going to be destroyed")
-        if self.tests:
+        if self.tests or quiet:
             destroy = True
         else:
             destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
         if not destroy:
             sys.exit()
         if destroy:
-            meta.Base.metadata.drop_all()
+            Base.metadata.drop_all()
 
         checkfirst = not override
-        meta.Base.metadata.create_all(checkfirst=checkfirst)
+        Base.metadata.create_all(checkfirst=checkfirst)
         log.info('Created tables for %s' % self.dbname)
 
     def set_db_version(self):
@@ -178,6 +181,44 @@ class DbManage(object):
             def step_5(self):
                 pass
 
+            def step_6(self):
+                print ('re-checking permissions')
+                self.klass.create_permissions()
+
+                print ('installing new hooks')
+                hooks4 = RhodeCodeUi()
+                hooks4.ui_section = 'hooks'
+                hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
+                hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
+                Session().add(hooks4)
+
+                hooks6 = RhodeCodeUi()
+                hooks6.ui_section = 'hooks'
+                hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
+                hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
+                Session().add(hooks6)
+
+                print ('installing hgsubversion option')
+                # enable hgsubversion disabled by default
+                hgsubversion = RhodeCodeUi()
+                hgsubversion.ui_section = 'extensions'
+                hgsubversion.ui_key = 'hgsubversion'
+                hgsubversion.ui_value = ''
+                hgsubversion.ui_active = False
+                Session().add(hgsubversion)
+
+                print ('installing hg git option')
+                # enable hggit disabled by default
+                hggit = RhodeCodeUi()
+                hggit.ui_section = 'extensions'
+                hggit.ui_key = 'hggit'
+                hggit.ui_value = ''
+                hggit.ui_active = False
+                Session().add(hggit)
+
+                print ('re-check default permissions')
+                self.klass.populate_default_permissions()
+
         upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
 
         # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
@@ -274,9 +315,9 @@ class DbManage(object):
             self.create_user(username, password, email, True)
         else:
             log.info('creating admin and regular test users')
-            from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
-            TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
-            TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
+            from rhodecode.tests import TEST_USER_ADMIN_LOGIN, \
+            TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL, \
+            TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS, \
             TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
             TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
 
@@ -305,43 +346,63 @@ class DbManage(object):
         hooks1.ui_key = hooks1_key
         hooks1.ui_value = 'hg update >&2'
         hooks1.ui_active = False
+        self.sa.add(hooks1)
 
         hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
         hooks2_ = self.sa.query(RhodeCodeUi)\
             .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
-
         hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
         hooks2.ui_section = 'hooks'
         hooks2.ui_key = hooks2_key
         hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
+        self.sa.add(hooks2)
 
         hooks3 = RhodeCodeUi()
         hooks3.ui_section = 'hooks'
         hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
         hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
+        self.sa.add(hooks3)
 
         hooks4 = RhodeCodeUi()
         hooks4.ui_section = 'hooks'
-        hooks4.ui_key = RhodeCodeUi.HOOK_PULL
-        hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
+        hooks4.ui_key = RhodeCodeUi.HOOK_PRE_PUSH
+        hooks4.ui_value = 'python:rhodecode.lib.hooks.pre_push'
+        self.sa.add(hooks4)
 
-        # For mercurial 1.7 set backward comapatibility with format
-        dotencode_disable = RhodeCodeUi()
-        dotencode_disable.ui_section = 'format'
-        dotencode_disable.ui_key = 'dotencode'
-        dotencode_disable.ui_value = 'false'
+        hooks5 = RhodeCodeUi()
+        hooks5.ui_section = 'hooks'
+        hooks5.ui_key = RhodeCodeUi.HOOK_PULL
+        hooks5.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
+        self.sa.add(hooks5)
+
+        hooks6 = RhodeCodeUi()
+        hooks6.ui_section = 'hooks'
+        hooks6.ui_key = RhodeCodeUi.HOOK_PRE_PULL
+        hooks6.ui_value = 'python:rhodecode.lib.hooks.pre_pull'
+        self.sa.add(hooks6)
 
         # enable largefiles
         largefiles = RhodeCodeUi()
         largefiles.ui_section = 'extensions'
         largefiles.ui_key = 'largefiles'
         largefiles.ui_value = ''
+        self.sa.add(largefiles)
 
-        self.sa.add(hooks1)
-        self.sa.add(hooks2)
-        self.sa.add(hooks3)
-        self.sa.add(hooks4)
-        self.sa.add(largefiles)
+        # enable hgsubversion disabled by default
+        hgsubversion = RhodeCodeUi()
+        hgsubversion.ui_section = 'extensions'
+        hgsubversion.ui_key = 'hgsubversion'
+        hgsubversion.ui_value = ''
+        hgsubversion.ui_active = False
+        self.sa.add(hgsubversion)
+
+        # enable hggit disabled by default
+        hggit = RhodeCodeUi()
+        hggit.ui_section = 'extensions'
+        hggit.ui_key = 'hggit'
+        hggit.ui_value = ''
+        hggit.ui_active = False
+        self.sa.add(hggit)
 
     def create_ldap_options(self, skip_existing=False):
         """Creates ldap settings"""
@@ -443,18 +504,30 @@ class DbManage(object):
         paths.ui_key = '/'
         paths.ui_value = path
 
-        hgsettings1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
-        hgsettings2 = RhodeCodeSetting('title', 'RhodeCode')
-        hgsettings3 = RhodeCodeSetting('ga_code', '')
+        phases = RhodeCodeUi()
+        phases.ui_section = 'phases'
+        phases.ui_key = 'publish'
+        phases.ui_value = False
+
+        sett1 = RhodeCodeSetting('realm', 'RhodeCode authentication')
+        sett2 = RhodeCodeSetting('title', 'RhodeCode')
+        sett3 = RhodeCodeSetting('ga_code', '')
+
+        sett4 = RhodeCodeSetting('show_public_icon', True)
+        sett5 = RhodeCodeSetting('show_private_icon', True)
+        sett6 = RhodeCodeSetting('stylify_metatags', False)
 
         self.sa.add(web1)
         self.sa.add(web2)
         self.sa.add(web3)
         self.sa.add(web4)
         self.sa.add(paths)
-        self.sa.add(hgsettings1)
-        self.sa.add(hgsettings2)
-        self.sa.add(hgsettings3)
+        self.sa.add(sett1)
+        self.sa.add(sett2)
+        self.sa.add(sett3)
+        self.sa.add(sett4)
+        self.sa.add(sett5)
+        self.sa.add(sett6)
 
         self.create_ldap_options()
 
@@ -463,7 +536,7 @@ class DbManage(object):
     def create_user(self, username, password, email='', admin=False):
         log.info('creating user %s' % username)
         UserModel().create_or_update(username, password, email,
-                                     name='RhodeCode', lastname='Admin',
+                                     firstname='RhodeCode', lastname='Admin',
                                      active=True, admin=admin)
 
     def create_default_user(self):
@@ -472,64 +545,39 @@ class DbManage(object):
         UserModel().create_or_update(username='default',
                               password=str(uuid.uuid1())[:8],
                               email='anonymous@rhodecode.org',
-                              name='Anonymous', lastname='User')
+                              firstname='Anonymous', lastname='User')
 
     def create_permissions(self):
         # module.(access|create|change|delete)_[name]
         # module.(none|read|write|admin)
-        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', 'Hg Administrator'),
-         ('hg.create.repository', 'Repository create'),
-         ('hg.create.none', 'Repository creation disabled'),
-         ('hg.register.none', 'Register disabled'),
-         ('hg.register.manual_activate', 'Register new user with RhodeCode '
-                                         'without manual activation'),
-
-         ('hg.register.auto_activate', 'Register new user with RhodeCode '
-                                        'without auto activation'),
-        ]
-
-        for p in perms:
+        for p in Permission.PERMS:
             if not Permission.get_by_key(p[0]):
                 new_perm = Permission()
                 new_perm.permission_name = p[0]
-                new_perm.permission_longname = p[1]
+                new_perm.permission_longname = p[0]
                 self.sa.add(new_perm)
 
     def populate_default_permissions(self):
         log.info('creating default user permissions')
 
-        default_user = self.sa.query(User)\
-        .filter(User.username == 'default').scalar()
+        default_user = User.get_by_username('default')
 
-        reg_perm = UserToPerm()
-        reg_perm.user = default_user
-        reg_perm.permission = self.sa.query(Permission)\
-        .filter(Permission.permission_name == 'hg.register.manual_activate')\
-        .scalar()
+        for def_perm in ['hg.register.manual_activate', 'hg.create.repository',
+                         'hg.fork.repository', 'repository.read']:
 
-        create_repo_perm = UserToPerm()
-        create_repo_perm.user = default_user
-        create_repo_perm.permission = self.sa.query(Permission)\
-        .filter(Permission.permission_name == 'hg.create.repository')\
-        .scalar()
-
-        default_repo_perm = UserToPerm()
-        default_repo_perm.user = default_user
-        default_repo_perm.permission = self.sa.query(Permission)\
-        .filter(Permission.permission_name == 'repository.read')\
-        .scalar()
-
-        self.sa.add(reg_perm)
-        self.sa.add(create_repo_perm)
-        self.sa.add(default_repo_perm)
+            perm = self.sa.query(Permission)\
+             .filter(Permission.permission_name == def_perm)\
+             .scalar()
+            if not perm:
+                raise Exception(
+                  'CRITICAL: permission %s not found inside database !!'
+                  % def_perm
+                )
+            if not UserToPerm.query()\
+                .filter(UserToPerm.permission == perm)\
+                .filter(UserToPerm.user == default_user).scalar():
+                reg_perm = UserToPerm()
+                reg_perm.user = default_user
+                reg_perm.permission = perm
+                self.sa.add(reg_perm)
diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py b/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py
--- a/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/ansisql.py
@@ -94,6 +94,7 @@ class ANSIColumnGenerator(AlterTableVisi
 
         table = self.start_alter_table(column)
         self.append("ADD ")
+
         self.append(self.get_column_specification(column))
 
         for cons in column.constraints:
diff --git a/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py b/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py
--- a/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py
+++ b/rhodecode/lib/dbmigrate/migrate/changeset/databases/sqlite.py
@@ -49,6 +49,7 @@ class SQLiteHelper(SQLiteCommon):
         else:
             column = delta
             table = self._to_table(column.table)
+
         self.recreate_table(table,column,delta)
 
 class SQLiteColumnGenerator(SQLiteSchemaGenerator,
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_2_0.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
 """
-    rhodecode.model.db
-    ~~~~~~~~~~~~~~~~~~
+    rhodecode.model.db_1_2_0
+    ~~~~~~~~~~~~~~~~~~~~~~~~
 
-    Database Models for RhodeCode
+    Database Models for RhodeCode <=1.2.X
 
     :created_on: Apr 08, 2010
     :author: marcink
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
--- a/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
+++ b/rhodecode/lib/dbmigrate/schema/db_1_3_0.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
 """
-    rhodecode.model.db
-    ~~~~~~~~~~~~~~~~~~
+    rhodecode.model.db_1_3_0
+    ~~~~~~~~~~~~~~~~~~~~~~~~
 
-    Database Models for RhodeCode
+    Database Models for RhodeCode <=1.3.X
 
     :created_on: Apr 08, 2010
     :author: marcink
@@ -23,6 +23,1298 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see .
 
-#TODO: when branch 1.3 is finished replacem with db.py content
+import os
+import logging
+import datetime
+import traceback
+from collections import defaultdict
+
+from sqlalchemy import *
+from sqlalchemy.ext.hybrid import hybrid_property
+from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
+from beaker.cache import cache_region, region_invalidate
+
+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
+from rhodecode.lib.compat import json
+from rhodecode.lib.caching_query import FromCache
+
+from rhodecode.model.meta import Base, Session
+import hashlib
+
+
+log = logging.getLogger(__name__)
+
+#==============================================================================
+# BASE CLASSES
+#==============================================================================
+
+_hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
+
+
+class ModelSerializer(json.JSONEncoder):
+    """
+    Simple Serializer for JSON,
+
+    usage::
+
+        to make object customized for serialization implement a __json__
+        method that will return a dict for serialization into json
+
+    example::
+
+        class Task(object):
+
+            def __init__(self, name, value):
+                self.name = name
+                self.value = value
+
+            def __json__(self):
+                return dict(name=self.name,
+                            value=self.value)
+
+    """
+
+    def default(self, obj):
+
+        if hasattr(obj, '__json__'):
+            return obj.__json__()
+        else:
+            return json.JSONEncoder.default(self, obj)
+
+
+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
+        for k, val in getattr(self, '__json__', lambda: {})().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 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 '' % (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(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    def __init__(self, k='', v=''):
+        self.app_settings_name = k
+        self.app_settings_value = v
+
+    @validates('_app_settings_value')
+    def validate_settings_value(self, key, val):
+        assert type(val) == unicode
+        return val
+
+    @hybrid_property
+    def app_settings_value(self):
+        v = self._app_settings_value
+        if self.app_settings_name == 'ldap_active':
+            v = str2bool(v)
+        return v
+
+    @app_settings_value.setter
+    def app_settings_value(self, val):
+        """
+        Setter that will always make sure we use unicode in app_settings_value
+
+        :param val:
+        """
+        self._app_settings_value = safe_unicode(val)
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (
+            self.__class__.__name__,
+            self.app_settings_name, self.app_settings_value
+        )
+
+    @classmethod
+    def get_by_name(cls, ldap_key):
+        return cls.query()\
+            .filter(cls.app_settings_name == ldap_key).scalar()
+
+    @classmethod
+    def get_app_settings(cls, cache=False):
+
+        ret = cls.query()
+
+        if cache:
+            ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
+
+        if not ret:
+            raise Exception('Could not get application settings !')
+        settings = {}
+        for each in ret:
+            settings['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
+
+
+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 = 'pretxnchangegroup.push_logger'
+    HOOK_PULL = 'preoutgoing.pull_logger'
+
+    ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.ui_key == key)
+
+    @classmethod
+    def get_builtin_hooks(cls):
+        q = cls.query()
+        q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
+                                    cls.HOOK_REPO_SIZE,
+                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
+        return q.all()
+
+    @classmethod
+    def get_custom_hooks(cls):
+        q = cls.query()
+        q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
+                                    cls.HOOK_REPO_SIZE,
+                                    cls.HOOK_PUSH, cls.HOOK_PULL]))
+        q = q.filter(cls.ui_section == 'hooks')
+        return q.all()
+
+    @classmethod
+    def create_or_update_hook(cls, key, val):
+        new_ui = cls.get_by_key(key).scalar() or cls()
+        new_ui.ui_section = 'hooks'
+        new_ui.ui_active = True
+        new_ui.ui_key = key
+        new_ui.ui_value = val
+
+        Session.add(new_ui)
+
+
+class User(Base, BaseModel):
+    __tablename__ = 'users'
+    __table_args__ = (
+        UniqueConstraint('username'), UniqueConstraint('email'),
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'}
+    )
+    user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    active = Column("active", Boolean(), nullable=True, unique=None, default=None)
+    admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
+    name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
+    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    user_log = relationship('UserLog', cascade='all')
+    user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
+
+    repositories = relationship('Repository')
+    user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
+    repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
+    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')
+
+    @hybrid_property
+    def email(self):
+        return self._email
+
+    @email.setter
+    def email(self, val):
+        self._email = val.lower() if val else None
+
+    @property
+    def full_name(self):
+        return '%s %s' % (self.name, self.lastname)
+
+    @property
+    def full_name_or_username(self):
+        return ('%s %s' % (self.name, self.lastname)
+                if (self.name and self.lastname) else self.username)
+
+    @property
+    def full_contact(self):
+        return '%s %s <%s>' % (self.name, self.lastname, self.email)
+
+    @property
+    def short_contact(self):
+        return '%s %s' % (self.name, self.lastname)
+
+    @property
+    def is_admin(self):
+        return self.admin
+
+    def __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_api_key_%s" % email))
+        return q.scalar()
+
+    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 __json__(self):
+        return dict(
+            user_id=self.user_id,
+            first_name=self.name,
+            last_name=self.lastname,
+            email=self.email,
+            full_name=self.full_name,
+            full_name_or_username=self.full_name_or_username,
+            short_contact=self.short_contact,
+            full_contact=self.full_contact
+        )
+
+
+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=False, unique=None, default=None)
+    repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
+    repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
+
+    @property
+    def action_as_day(self):
+        return 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(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
+
+    members = relationship('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'' % (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)
+
+
+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'),
+        {'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(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
+    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
+    repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
+    user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
+    private = Column("private", Boolean(), nullable=True, unique=None, default=None)
+    enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
+    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
+    description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
+
+    fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
+    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
+
+    user = relationship('User')
+    fork = relationship('Repository', remote_side=repo_id)
+    group = relationship('RepoGroup')
+    repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
+    users_group_to_perm = relationship('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')
+
+    def __unicode__(self):
+        return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
+                                   self.repo_name)
+
+    @classmethod
+    def url_sep(cls):
+        return '/'
+
+    @classmethod
+    def get_by_repo_name(cls, repo_name):
+        q = Session.query(cls).filter(cls.repo_name == repo_name)
+        q = q.options(joinedload(Repository.fork))\
+                .options(joinedload(Repository.user))\
+                .options(joinedload(Repository.group))
+        return q.scalar()
+
+    @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 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)
+
+    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 mercurial import ui
+        from mercurial import config
+        baseui = ui.ui()
+
+        #clean the baseui object
+        baseui._ocfg = config.config()
+        baseui._ucfg = config.config()
+        baseui._tcfg = config.config()
+
+        ret = RhodeCodeUi.query()\
+            .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
+
+        hg_ui = ret
+        for ui_ in hg_ui:
+            if ui_.ui_active:
+                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)
+
+        return baseui
+
+    @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())
+
+    #==========================================================================
+    # SCM PROPERTIES
+    #==========================================================================
+
+    def get_changeset(self, rev):
+        return get_changeset_safe(self.scm_instance, rev)
+
+    @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 comments(self, revisions=None):
+        """
+        Returns comments for this repository grouped by revisions
 
-from rhodecode.model.db import *
+        :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
+
+    #==========================================================================
+    # 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(self.repo_name)
+
+    @LazyProperty
+    def scm_instance(self):
+        return self.__get_instance()
+
+    @property
+    def scm_instance_cached(self):
+        @cache_region('long_term')
+        def _c(repo_name):
+            return self.__get_instance()
+        rn = self.repo_name
+        log.debug('Getting cached instance of repo')
+        inv = self.invalidate
+        if inv is not None:
+            region_invalidate(_c, None, rn)
+            # update our cache
+            CacheInvalidation.set_valid(inv.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(length=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(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+
+    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):
+        from webhelpers.html import literal as _literal
+        repo_groups = [('', '')]
+        sep = ' » '
+        _name = lambda k: _literal(sep.join(k))
+
+        repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
+                              for x in cls.query().all()])
+
+        repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
+        return repo_groups
+
+    @classmethod
+    def url_sep(cls):
+        return '/'
+
+    @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 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__ = (
+        {'extend_existing': True, 'mysql_engine':'InnoDB',
+         'mysql_charset': 'utf8'},
+    )
+    permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
+    permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    permission_longname = Column("permission_longname", String(length=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' %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' %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'),
+        {'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(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
+    cache_args = Column("cache_args", String(length=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)
+    @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 = ''
+        iid = rhodecode.CONFIG.get('instance_id')
+        if iid:
+            prefix = iid
+        return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
+
+    @classmethod
+    def get_by_key(cls, key):
+        return cls.query().filter(cls.cache_key == key).scalar()
+
+    @classmethod
+    def _get_or_create_key(cls, key, prefix, org_key):
+        inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
+        if not inv_obj:
+            try:
+                inv_obj = CacheInvalidation(key, org_key)
+                Session.add(inv_obj)
+                Session.commit()
+            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:
+        """
+
+        key, _prefix, _org_key = cls._get_key(key)
+        inv = cls._get_or_create_key(key, _prefix, _org_key)
+
+        if inv and inv.cache_active is False:
+            return inv
+
+    @classmethod
+    def set_invalidate(cls, key):
+        """
+        Mark this Cache key for invalidation
+
+        :param key:
+        """
+
+        key, _prefix, _org_key = cls._get_key(key)
+        inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
+        log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
+                                                             _org_key))
+        try:
+            for inv_obj in inv_objs:
+                if inv_obj:
+                    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()
+
+
+class ChangesetComment(Base, BaseModel):
+    __tablename__ = 'changeset_comments'
+    __table_args__ = (
+        {'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=False)
+    line_no = Column('line_no', Unicode(10), 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', Unicode(25000), nullable=False)
+    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
+
+    author = relationship('User', lazy='joined')
+    repo = relationship('Repository')
+
+    @classmethod
+    def get_users(cls, revision):
+        """
+        Returns user associated with this changesetComment. ie those
+        who actually commented
+
+        :param cls:
+        :param revision:
+        """
+        return Session.query(User)\
+                .filter(cls.revision == revision)\
+                .join(ChangesetComment.author).all()
+
+
+class Notification(Base, BaseModel):
+    __tablename__ = 'notifications'
+    __table_args__ = (
+        {'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'
+
+    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
+    subject = Column('subject', Unicode(512), nullable=True)
+    body = Column('body', Unicode(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).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)
+
+## this is migration from 1_4_0, but now it's here to overcome a problem of
+## attaching a FK to this from 1_3_0 !
+
+
+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)
\ No newline at end of file
diff --git a/rhodecode/lib/dbmigrate/schema/db_1_4_0.py b/rhodecode/lib/dbmigrate/schema/db_1_4_0.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/dbmigrate/schema/db_1_4_0.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+"""
+    rhodecode.model.db_1_4_0
+    ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Database Models for RhodeCode <=1.4.X
+
+    :created_on: Apr 08, 2010
+    :author: marcink
+    :copyright: (C) 2010-2012 Marcin Kuzminski 
+    :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 .
+
+#TODO: replace that will db.py content after 1.5 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
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/dbmigrate/versions/006_version_1_4_0.py
@@ -0,0 +1,186 @@
+import logging
+import datetime
+
+from sqlalchemy import *
+from sqlalchemy.exc import DatabaseError
+from sqlalchemy.orm import relation, backref, class_mapper
+from sqlalchemy.orm.session import Session
+from sqlalchemy.ext.declarative import declarative_base
+
+from rhodecode.lib.dbmigrate.migrate import *
+from rhodecode.lib.dbmigrate.migrate.changeset import *
+
+from rhodecode.model.meta import Base
+from rhodecode.model import meta
+
+log = logging.getLogger(__name__)
+
+
+def upgrade(migrate_engine):
+    """
+    Upgrade operations go here.
+    Don't create your own engine; bind migrate_engine to your metadata
+    """
+
+    #==========================================================================
+    # USEREMAILMAP
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import UserEmailMap
+    tbl = UserEmailMap.__table__
+    tbl.create()
+    #==========================================================================
+    # PULL REQUEST
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import PullRequest
+    tbl = PullRequest.__table__
+    tbl.create()
+
+    #==========================================================================
+    # PULL REQUEST REVIEWERS
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import PullRequestReviewers
+    tbl = PullRequestReviewers.__table__
+    tbl.create()
+
+    #==========================================================================
+    # CHANGESET STATUS
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_4_0 import ChangesetStatus
+    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
+
+    #==========================================================================
+    # USERS TABLE
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import User
+    tbl = User.__table__
+
+    # change column name -> firstname
+    col = User.__table__.columns.name
+    col.alter(index=Index('u_username_idx', 'username'))
+    col.alter(index=Index('u_email_idx', 'email'))
+    col.alter(name="firstname", table=tbl)
+
+    # add inherit_default_permission column
+    inherit_default_permissions = Column("inherit_default_permissions",
+                                         Boolean(), nullable=True, unique=None,
+                                         default=True)
+    inherit_default_permissions.create(table=tbl)
+    inherit_default_permissions.alter(nullable=False, default=True, table=tbl)
+
+    #==========================================================================
+    # USERS GROUP TABLE
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import UsersGroup
+    tbl = UsersGroup.__table__
+    # add inherit_default_permission column
+    gr_inherit_default_permissions = Column(
+                                    "users_group_inherit_default_permissions",
+                                    Boolean(), nullable=True, unique=None,
+                                    default=True)
+    gr_inherit_default_permissions.create(table=tbl)
+    gr_inherit_default_permissions.alter(nullable=False, default=True, table=tbl)
+
+    #==========================================================================
+    # REPOSITORIES
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import Repository
+    tbl = Repository.__table__
+
+    # add enable locking column
+    enable_locking = Column("enable_locking", Boolean(), nullable=True,
+                            unique=None, default=False)
+    enable_locking.create(table=tbl)
+    enable_locking.alter(nullable=False, default=False, table=tbl)
+
+    # add locked column
+    _locked = Column("locked", String(255), nullable=True, unique=False,
+                     default=None)
+    _locked.create(table=tbl)
+
+    #add langing revision column
+    landing_rev = Column("landing_revision", String(255), nullable=True,
+                         unique=False, default='tip')
+    landing_rev.create(table=tbl)
+    landing_rev.alter(nullable=False, default='tip', table=tbl)
+
+    #==========================================================================
+    # GROUPS
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import RepoGroup
+    tbl = RepoGroup.__table__
+
+    # add enable locking column
+    enable_locking = Column("enable_locking", Boolean(), nullable=True,
+                            unique=None, default=False)
+    enable_locking.create(table=tbl)
+    enable_locking.alter(nullable=False, default=False)
+
+    #==========================================================================
+    # CACHE INVALIDATION
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import CacheInvalidation
+    tbl = CacheInvalidation.__table__
+
+    # add INDEX for cache keys
+    col = CacheInvalidation.__table__.columns.cache_key
+    col.alter(index=Index('key_idx', 'cache_key'))
+
+    #==========================================================================
+    # NOTIFICATION
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import Notification
+    tbl = Notification.__table__
+
+    # add index for notification type
+    col = Notification.__table__.columns.type
+    col.alter(index=Index('notification_type_idx', 'type'),)
+
+    #==========================================================================
+    # CHANGESET_COMMENTS
+    #==========================================================================
+    from rhodecode.lib.dbmigrate.schema.db_1_3_0 import ChangesetComment
+
+    tbl = ChangesetComment.__table__
+    col = ChangesetComment.__table__.columns.revision
+
+    # add index for revisions
+    col.alter(index=Index('cc_revision_idx', 'revision'),)
+
+    # add hl_lines column
+    hl_lines = Column('hl_lines', Unicode(512), nullable=True)
+    hl_lines.create(table=tbl)
+
+    # add created_on column
+    created_on = Column('created_on', DateTime(timezone=False), nullable=True,
+                        default=datetime.datetime.now)
+    created_on.create(table=tbl)
+    created_on.alter(nullable=False, default=datetime.datetime.now)
+
+    modified_at = Column('modified_at', DateTime(timezone=False), nullable=False,
+                         default=datetime.datetime.now)
+    modified_at.alter(type=DateTime(timezone=False), table=tbl)
+
+    # add FK to pull_request
+    pull_request_id = Column("pull_request_id", Integer(),
+                             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
+
+
+def downgrade(migrate_engine):
+    meta = MetaData()
+    meta.bind = migrate_engine
diff --git a/rhodecode/lib/diffs.py b/rhodecode/lib/diffs.py
--- a/rhodecode/lib/diffs.py
+++ b/rhodecode/lib/diffs.py
@@ -28,14 +28,22 @@
 import re
 import difflib
 import markupsafe
+
 from itertools import tee, imap
 
+from mercurial import patch
+from mercurial.mdiff import diffopts
+from mercurial.bundlerepo import bundlerepository
+
 from pylons.i18n.translation import _
 
+from rhodecode.lib.compat import BytesIO
+from rhodecode.lib.vcs.utils.hgcompat import localrepo
 from rhodecode.lib.vcs.exceptions import VCSError
 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
+from rhodecode.lib.vcs.backends.base import EmptyChangeset
 from rhodecode.lib.helpers import escape
-from rhodecode.lib.utils import EmptyChangeset
+from rhodecode.lib.utils import make_ui
 
 
 def wrap_to_table(str_):
@@ -75,7 +83,7 @@ def wrapped_diff(filenode_old, filenode_
         stats = diff_processor.stat()
         size = len(diff or '')
     else:
-        diff = wrap_to_table(_('Changeset was to big and was cut off, use '
+        diff = wrap_to_table(_('Changeset was too big and was cut off, use '
                                'diff menu to display this diff'))
         stats = (0, 0)
         size = 0
@@ -127,8 +135,9 @@ class DiffProcessor(object):
     can be used to render it in a HTML template.
     """
     _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
+    _newline_marker = '\\ No newline at end of file\n'
 
-    def __init__(self, diff, differ='diff', format='udiff'):
+    def __init__(self, diff, differ='diff', format='gitdiff'):
         """
         :param diff:   a text in diff format or generator
         :param format: format of diff passed, `udiff` or `gitdiff`
@@ -171,7 +180,7 @@ class DiffProcessor(object):
 
     def _extract_rev(self, line1, line2):
         """
-        Extract the filename and revision hint from a line.
+        Extract the operation (A/M/D), filename and revision hint from a line.
         """
 
         try:
@@ -189,11 +198,15 @@ class DiffProcessor(object):
                 filename = (old_filename
                             if old_filename != '/dev/null' else new_filename)
 
-                return filename, new_rev, old_rev
+                operation = 'D' if new_filename == '/dev/null' else None
+                if not operation:
+                    operation = 'M' if old_filename != '/dev/null' else 'A'
+
+                return operation, filename, new_rev, old_rev
         except (ValueError, IndexError):
             pass
 
-        return None, None, None
+        return None, None, None, None
 
     def _parse_gitdiff(self, diffiterator):
         def line_decoder(l):
@@ -278,7 +291,7 @@ class DiffProcessor(object):
             do(line)
             do(next_)
 
-    def _parse_udiff(self):
+    def _parse_udiff(self, inline_diff=True):
         """
         Parse the diff an return data for the template.
         """
@@ -286,8 +299,6 @@ class DiffProcessor(object):
         files = []
         try:
             line = lineiter.next()
-            # skip first context
-            skipfirst = True
             while 1:
                 # continue until we found the old file
                 if not line.startswith('--- '):
@@ -295,13 +306,16 @@ class DiffProcessor(object):
                     continue
 
                 chunks = []
-                filename, old_rev, new_rev = \
+                stats = [0, 0]
+                operation, filename, old_rev, new_rev = \
                     self._extract_rev(line, lineiter.next())
                 files.append({
                     'filename':         filename,
                     'old_revision':     old_rev,
                     'new_revision':     new_rev,
-                    'chunks':           chunks
+                    'chunks':           chunks,
+                    'operation':        operation,
+                    'stats':            stats,
                 })
 
                 line = lineiter.next()
@@ -317,27 +331,33 @@ class DiffProcessor(object):
                         [int(x or 1) for x in match.groups()[:-1]]
                     old_line -= 1
                     new_line -= 1
-                    context = len(match.groups()) == 5
+                    gr = match.groups()
+                    context = len(gr) == 5
                     old_end += old_line
                     new_end += new_line
 
                     if context:
-                        if not skipfirst:
+                        # skip context only if it's first line
+                        if int(gr[0]) > 1:
                             lines.append({
                                 'old_lineno': '...',
                                 'new_lineno': '...',
                                 'action':     'context',
                                 'line':       line,
                             })
-                        else:
-                            skipfirst = False
 
                     line = lineiter.next()
+
                     while old_line < old_end or new_line < new_end:
                         if line:
-                            command, line = line[0], line[1:]
+                            command = line[0]
+                            if command in ['+', '-', ' ']:
+                                #only modify the line if it's actually a diff
+                                # thing
+                                line = line[1:]
                         else:
                             command = ' '
+
                         affects_old = affects_new = False
 
                         # ignore those if we don't expect them
@@ -346,51 +366,67 @@ class DiffProcessor(object):
                         elif command == '+':
                             affects_new = True
                             action = 'add'
+                            stats[0] += 1
                         elif command == '-':
                             affects_old = True
                             action = 'del'
+                            stats[1] += 1
                         else:
                             affects_old = affects_new = True
                             action = 'unmod'
 
-                        old_line += affects_old
-                        new_line += affects_new
-                        lines.append({
-                            'old_lineno':   affects_old and old_line or '',
-                            'new_lineno':   affects_new and new_line or '',
-                            'action':       action,
-                            'line':         line
-                        })
+                        if line != self._newline_marker:
+                            old_line += affects_old
+                            new_line += affects_new
+                            lines.append({
+                                'old_lineno':   affects_old and old_line or '',
+                                'new_lineno':   affects_new and new_line or '',
+                                'action':       action,
+                                'line':         line
+                            })
+
                         line = lineiter.next()
+                        if line == self._newline_marker:
+                            # we need to append to lines, since this is not
+                            # counted in the line specs of diff
+                            lines.append({
+                                'old_lineno':   '...',
+                                'new_lineno':   '...',
+                                'action':       'context',
+                                'line':         line
+                            })
 
         except StopIteration:
             pass
 
+        sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
+        if inline_diff is False:
+            return sorted(files, key=sorter)
+
         # highlight inline changes
-        for _ in files:
-            for chunk in chunks:
+        for diff_data in files:
+            for chunk in diff_data['chunks']:
                 lineiter = iter(chunk)
-                #first = True
                 try:
                     while 1:
                         line = lineiter.next()
-                        if line['action'] != 'unmod':
+                        if line['action'] not in ['unmod', 'context']:
                             nextline = lineiter.next()
-                            if nextline['action'] == 'unmod' or \
+                            if nextline['action'] in ['unmod', 'context'] or \
                                nextline['action'] == line['action']:
                                 continue
                             self.differ(line, nextline)
                 except StopIteration:
                     pass
 
-        return files
+        return sorted(files, key=sorter)
 
-    def prepare(self):
+    def prepare(self, inline_diff=True):
         """
         Prepare the passed udiff for HTML rendering. It'l return a list
         of dicts
         """
-        return self._parse_udiff()
+        return self._parse_udiff(inline_diff=inline_diff)
 
     def _safe_id(self, idstring):
         """Make a string safe for including in an id attribute.
@@ -424,9 +460,9 @@ class DiffProcessor(object):
 
     def as_html(self, table_class='code-difftable', line_class='line',
                 new_lineno_class='lineno old', old_lineno_class='lineno new',
-                code_class='code', enable_comments=False):
+                code_class='code', enable_comments=False, diff_lines=None):
         """
-        Return udiff as html table with customized css classes
+        Return given diff as html table with customized css classes
         """
         def _link_to_if(condition, label, url):
             """
@@ -440,7 +476,8 @@ class DiffProcessor(object):
                 }
             else:
                 return label
-        diff_lines = self.prepare()
+        if diff_lines is None:
+            diff_lines = self.prepare()
         _html_empty = True
         _html = []
         _html.append('''\n''' % {
@@ -522,3 +559,78 @@ class DiffProcessor(object):
         Returns tuple of added, and removed lines for this instance
         """
         return self.adds, self.removes
+
+
+class InMemoryBundleRepo(bundlerepository):
+    def __init__(self, ui, path, bundlestream):
+        self._tempparent = None
+        localrepo.localrepository.__init__(self, ui, path)
+        self.ui.setconfig('phases', 'publish', False)
+
+        self.bundle = bundlestream
+
+        # dict with the mapping 'filename' -> position in the bundle
+        self.bundlefilespos = {}
+
+
+def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
+    """
+    General differ between branches, bookmarks or separate but releated
+    repositories
+
+    :param org_repo:
+    :type org_repo:
+    :param org_ref:
+    :type org_ref:
+    :param other_repo:
+    :type other_repo:
+    :param other_ref:
+    :type other_ref:
+    """
+
+    bundlerepo = None
+    ignore_whitespace = False
+    context = 3
+    org_repo = org_repo.scm_instance._repo
+    other_repo = other_repo.scm_instance._repo
+    opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
+    org_ref = org_ref[1]
+    other_ref = other_ref[1]
+
+    if org_repo != other_repo:
+
+        common, incoming, rheads = discovery_data
+        other_repo_peer = localrepo.locallegacypeer(other_repo.local())
+        # create a bundle (uncompressed if other repo is not local)
+        if other_repo_peer.capable('getbundle') and incoming:
+            # disable repo hooks here since it's just bundle !
+            # patch and reset hooks section of UI config to not run any
+            # hooks on fetching archives with subrepos
+            for k, _ in other_repo.ui.configitems('hooks'):
+                other_repo.ui.setconfig('hooks', k, None)
+
+            unbundle = other_repo.getbundle('incoming', common=common,
+                                            heads=rheads)
+
+            buf = BytesIO()
+            while True:
+                chunk = unbundle._stream.read(1024 * 4)
+                if not chunk:
+                    break
+                buf.write(chunk)
+
+            buf.seek(0)
+            # replace chunked _stream with data that can do tell() and seek()
+            unbundle._stream = buf
+
+            ui = make_ui('db')
+            bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
+                                            bundlestream=unbundle)
+
+        return ''.join(patch.diff(bundlerepo or org_repo,
+                                  node1=org_repo[org_ref].node(),
+                                  node2=other_repo[other_ref].node(),
+                                  opts=opts))
+    else:
+        return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
+                                  opts=opts))
diff --git a/rhodecode/lib/exceptions.py b/rhodecode/lib/exceptions.py
--- a/rhodecode/lib/exceptions.py
+++ b/rhodecode/lib/exceptions.py
@@ -23,6 +23,8 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see .
 
+from webob.exc import HTTPClientError
+
 
 class LdapUsernameError(Exception):
     pass
@@ -50,3 +52,20 @@ class UserOwnsReposException(Exception):
 
 class UsersGroupsAssignedException(Exception):
     pass
+
+
+class StatusChangeOnClosedPullRequestError(Exception):
+    pass
+
+
+class HTTPLockedRC(HTTPClientError):
+    """
+    Special Exception For locked Repos in RhodeCode
+    """
+    code = 423
+    title = explanation = 'Repository Locked'
+
+    def __init__(self, reponame, username, *args, **kwargs):
+        self.title = self.explanation = ('Repository `%s` locked by '
+                                         'user `%s`' % (reponame, username))
+        super(HTTPLockedRC, self).__init__(*args, **kwargs)
diff --git a/rhodecode/lib/ext_json.py b/rhodecode/lib/ext_json.py
--- a/rhodecode/lib/ext_json.py
+++ b/rhodecode/lib/ext_json.py
@@ -60,7 +60,7 @@ def _obj_dump(obj):
 # Import simplejson
 try:
     # import simplejson initially
-    import simplejson as _sj
+    import simplejson
 
     def extended_encode(obj):
         try:
@@ -70,37 +70,42 @@ try:
         raise TypeError("%r is not JSON serializable" % (obj,))
     # we handle decimals our own it makes unified behavior of json vs
     # simplejson
-    _sj.dumps = functools.partial(_sj.dumps, default=extended_encode,
-                                  use_decimal=False)
-    _sj.dump = functools.partial(_sj.dump, default=extended_encode,
-                                 use_decimal=False)
-    simplejson = _sj
-
+    simplejson.dumps = functools.partial(simplejson.dumps,
+                                         default=extended_encode,
+                                         use_decimal=False)
+    simplejson.dump = functools.partial(simplejson.dump,
+                                        default=extended_encode,
+                                        use_decimal=False)
 except ImportError:
     # no simplejson set it to None
-    _sj = None
+    simplejson = None
 
 
 try:
     # simplejson not found try out regular json module
-    import json as _json
+    import json
 
     # extended JSON encoder for json
-    class ExtendedEncoder(_json.JSONEncoder):
+    class ExtendedEncoder(json.JSONEncoder):
         def default(self, obj):
             try:
                 return _obj_dump(obj)
             except NotImplementedError:
                 pass
-            return _json.JSONEncoder.default(self, obj)
+            return json.JSONEncoder.default(self, obj)
     # monkey-patch JSON encoder to use extended version
-    _json.dumps = functools.partial(_json.dumps, cls=ExtendedEncoder)
-    _json.dump = functools.partial(_json.dump, cls=ExtendedEncoder)
-    stdlib = _json
+    json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
+    json.dump = functools.partial(json.dump, cls=ExtendedEncoder)
+
 except ImportError:
-    _json = None
+    json = None
+
+stdlib = json
 
 # set all available json modules
-simplejson = _sj
-stdjson = _json
-json = _sj if _sj else _json
+if simplejson:
+    json = simplejson
+elif json:
+    json = json
+else:
+    raise ImportError('Could not find any json modules')
diff --git a/rhodecode/lib/graphmod.py b/rhodecode/lib/graphmod.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/lib/graphmod.py
@@ -0,0 +1,127 @@
+"""
+Modified mercurial DAG graph functions that re-uses VCS structure
+
+It allows to have a shared codebase for DAG generation for hg and git repos
+"""
+
+nullrev = -1
+
+
+def grandparent(parentrev_func, lowestrev, roots, head):
+    """
+    Return all ancestors of head in roots which revision is
+    greater or equal to lowestrev.
+    """
+    pending = set([head])
+    seen = set()
+    kept = set()
+    llowestrev = max(nullrev, lowestrev)
+    while pending:
+        r = pending.pop()
+        if r >= llowestrev and r not in seen:
+            if r in roots:
+                kept.add(r)
+            else:
+                pending.update([p for p in parentrev_func(r)])
+            seen.add(r)
+    return sorted(kept)
+
+
+def _dagwalker(repo, revs, alias):
+    if not revs:
+        return
+
+    if alias == 'hg':
+        cl = repo._repo.changelog.parentrevs
+        repo = repo
+    elif alias == 'git':
+        def cl(rev):
+            return [x.revision for x in repo[rev].parents()]
+        repo = repo
+
+    lowestrev = min(revs)
+    gpcache = {}
+
+    knownrevs = set(revs)
+    for rev in revs:
+        ctx = repo[rev]
+        parents = sorted(set([p.revision for p in ctx.parents
+                              if p.revision in knownrevs]))
+        mpars = [p.revision for p in ctx.parents if
+                 p.revision != nullrev and p.revision not in parents]
+
+        for mpar in mpars:
+            gp = gpcache.get(mpar)
+            if gp is None:
+                gp = gpcache[mpar] = grandparent(cl, lowestrev, revs, mpar)
+            if not gp:
+                parents.append(mpar)
+            else:
+                parents.extend(g for g in gp if g not in parents)
+
+        yield (ctx.revision, 'C', ctx, parents)
+
+
+def _colored(dag):
+    """annotates a DAG with colored edge information
+
+    For each DAG node this function emits tuples::
+
+      (id, type, data, (col, color), [(col, nextcol, color)])
+
+    with the following new elements:
+
+      - Tuple (col, color) with column and color index for the current node
+      - A list of tuples indicating the edges between the current node and its
+        parents.
+    """
+    seen = []
+    colors = {}
+    newcolor = 1
+
+    getconf = lambda rev: {}
+
+    for (cur, type, data, parents) in dag:
+
+        # Compute seen and next
+        if cur not in seen:
+            seen.append(cur)  # new head
+            colors[cur] = newcolor
+            newcolor += 1
+
+        col = seen.index(cur)
+        color = colors.pop(cur)
+        next = seen[:]
+
+        # Add parents to next
+        addparents = [p for p in parents if p not in next]
+        next[col:col + 1] = addparents
+
+        # Set colors for the parents
+        for i, p in enumerate(addparents):
+            if not i:
+                colors[p] = color
+            else:
+                colors[p] = newcolor
+                newcolor += 1
+
+        # Add edges to the graph
+        edges = []
+        for ecol, eid in enumerate(seen):
+            if eid in next:
+                bconf = getconf(eid)
+                edges.append((
+                    ecol, next.index(eid), colors[eid],
+                    bconf.get('width', -1),
+                    bconf.get('color', '')))
+            elif eid == cur:
+                for p in parents:
+                    bconf = getconf(p)
+                    edges.append((
+                        ecol, next.index(p), color,
+                        bconf.get('width', -1),
+                        bconf.get('color', '')))
+
+        # Yield and move on
+        yield (cur, type, data, (col, color), edges)
+        seen = next
diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -9,6 +9,7 @@ import StringIO
 import urllib
 import math
 import logging
+import re
 
 from datetime import datetime
 from pygments.formatters.html import HtmlFormatter
@@ -40,12 +41,31 @@ from webhelpers.html.tags import _set_in
 from rhodecode.lib.annotate import annotate_highlight
 from rhodecode.lib.utils import repo_name_slug
 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
-    get_changeset_safe
+    get_changeset_safe, datetime_to_time, time_to_datetime
 from rhodecode.lib.markup_renderer import MarkupRenderer
+from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
+from rhodecode.lib.vcs.backends.base import BaseChangeset
+from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
+from rhodecode.model.changeset_status import ChangesetStatusModel
+from rhodecode.model.db import URL_SEP, Permission
 
 log = logging.getLogger(__name__)
 
 
+html_escape_table = {
+    "&": "&",
+    '"': """,
+    "'": "'",
+    ">": ">",
+    "<": "<",
+}
+
+
+def html_escape(text):
+    """Produce entities within text."""
+    return "".join(html_escape_table.get(c,c) for c in text)
+
+
 def shorter(text, size=20):
     postfix = '...'
     if len(text) > size:
@@ -105,7 +125,7 @@ class _GetError(object):
 
     def __call__(self, field_name, form_errors):
         tmpl = """%s"""
-        if form_errors and form_errors.has_key(field_name):
+        if form_errors and field_name in form_errors:
             return literal(tmpl % form_errors.get(field_name))
 
 get_error = _GetError()
@@ -114,12 +134,15 @@ get_error = _GetError()
 class _ToolTip(object):
 
     def __call__(self, tooltip_title, trim_at=50):
-        """Special function just to wrap our text into nice formatted
+        """
+        Special function just to wrap our text into nice formatted
         autowrapped text
 
         :param tooltip_title:
         """
-        return escape(tooltip_title)
+        tooltip_title = escape(tooltip_title)
+        tooltip_title = tooltip_title.replace('<', '<').replace('>', '>')
+        return tooltip_title
 tooltip = _ToolTip()
 
 
@@ -130,7 +153,8 @@ class _FilesBreadCrumbs(object):
             paths = safe_unicode(paths)
         url_l = [link_to(repo_name, url('files_home',
                                         repo_name=repo_name,
-                                        revision=rev, f_path=''))]
+                                        revision=rev, f_path=''),
+                         class_='ypjax-link')]
         paths_l = paths.split('/')
         for cnt, p in enumerate(paths_l):
             if p != '':
@@ -139,7 +163,8 @@ class _FilesBreadCrumbs(object):
                                          repo_name=repo_name,
                                          revision=rev,
                                          f_path='/'.join(paths_l[:cnt + 1])
-                                         )
+                                         ),
+                                     class_='ypjax-link'
                                      )
                              )
 
@@ -333,7 +358,7 @@ flash = _Flash()
 #==============================================================================
 from rhodecode.lib.vcs.utils import author_name, author_email
 from rhodecode.lib.utils2 import credentials_filter, age as _age
-from rhodecode.model.db import User
+from rhodecode.model.db import User, ChangesetStatus
 
 age = lambda  x: _age(x)
 capitalize = lambda x: x.capitalize()
@@ -342,6 +367,14 @@ short_id = lambda x: x[:12]
 hide_credentials = lambda x: ''.join(credentials_filter(x))
 
 
+def fmt_date(date):
+    if date:
+        _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
+        return date.strftime(_fmt).decode('utf8')
+
+    return ""
+
+
 def is_git(repository):
     if hasattr(repository, 'alias'):
         _type = repository.alias
@@ -363,8 +396,14 @@ def is_hg(repository):
 
 
 def email_or_none(author):
+    # extract email from the commit string
     _email = email(author)
     if _email != '':
+        # check it against RhodeCode database, and use the MAIN email for this
+        # user
+        user = User.get_by_email(_email, case_insensitive=True, cache=True)
+        if user is not None:
+            return user.email
         return _email
 
     # See if it contains a username we can get an email from
@@ -377,9 +416,9 @@ def email_or_none(author):
     return None
 
 
-def person(author):
+def person(author, show_attr="username_and_name"):
     # attr to return from fetched user
-    person_getter = lambda usr: usr.username
+    person_getter = lambda usr: getattr(usr, show_attr)
 
     # Valid email in the attribute passed, see if they're in the system
     _email = email(author)
@@ -400,6 +439,39 @@ def person(author):
     return _author
 
 
+def person_by_id(id_, show_attr="username_and_name"):
+    # attr to return from fetched user
+    person_getter = lambda usr: getattr(usr, show_attr)
+
+    #maybe it's an ID ?
+    if str(id_).isdigit() or isinstance(id_, int):
+        id_ = int(id_)
+        user = User.get(id_)
+        if user is not None:
+            return person_getter(user)
+    return id_
+
+
+def desc_stylize(value):
+    """
+    converts tags from value into html equivalent
+
+    :param value:
+    """
+    value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
+                   '
see => \\1
', value) + value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]', + '', value) + value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]', + '
\\1 => \\2
', value) + value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]', + '
\\2
', value) + value = re.sub(r'\[([a-z]+)\]', + '
\\1
', value) + + return value + + def bool2icon(value): """Returns True/False values represented as small html image of true/false icons @@ -447,22 +519,30 @@ def action_parser(user_log, feed=False): repo = user_log.repository.scm_instance - message = lambda rev: rev.message - lnk = lambda rev, repo_name: ( - link_to('r%s:%s' % (rev.revision, rev.short_id), - url('changeset_home', repo_name=repo_name, - revision=rev.raw_id), - title=tooltip(message(rev)), class_='tooltip') - ) + def lnk(rev, repo_name): + + if isinstance(rev, BaseChangeset): + lbl = 'r%s:%s' % (rev.revision, rev.short_id) + _url = url('changeset_home', repo_name=repo_name, + revision=rev.raw_id) + title = tooltip(rev.message) + else: + lbl = '%s' % rev + _url = '#' + title = _('Changeset not found') + + return link_to(lbl, _url, title=title, class_='tooltip',) revs = [] if len(filter(lambda v: v != '', revs_ids)) > 0: - # get only max revs_top_limit of changeset for performance/ui reasons - revs = [ - x for x in repo.get_changesets(revs_ids[0], - revs_ids[:revs_top_limit][-1]) - ] - + for rev in revs_ids[:revs_top_limit]: + try: + rev = repo.get_changeset(rev) + revs.append(rev) + except ChangesetDoesNotExistError: + log.error('cannot find revision %s in this repo' % rev) + revs.append(rev) + continue cs_links = [] cs_links.append(" " + ', '.join( [lnk(rev, repo_name) for rev in revs[:revs_limit]] @@ -526,22 +606,68 @@ def action_parser(user_log, feed=False): return _('fork name ') + str(link_to(action_params, url('summary_home', repo_name=repo_name,))) - action_map = {'user_deleted_repo': (_('[deleted] repository'), None), - 'user_created_repo': (_('[created] repository'), None), - 'user_created_fork': (_('[created] repository as fork'), None), - 'user_forked_repo': (_('[forked] repository'), get_fork_name), - 'user_updated_repo': (_('[updated] repository'), None), - 'admin_deleted_repo': (_('[delete] repository'), None), - 'admin_created_repo': (_('[created] repository'), None), - 'admin_forked_repo': (_('[forked] repository'), None), - 'admin_updated_repo': (_('[updated] repository'), None), - 'push': (_('[pushed] into'), get_cs_links), - 'push_local': (_('[committed via RhodeCode] into'), get_cs_links), - 'push_remote': (_('[pulled from remote] into'), get_cs_links), - 'pull': (_('[pulled] from'), None), - 'started_following_repo': (_('[started following] repository'), None), - 'stopped_following_repo': (_('[stopped following] repository'), None), - } + def get_user_name(): + user_name = action_params + return user_name + + def get_users_group(): + group_name = action_params + return group_name + + def get_pull_request(): + pull_request_id = action_params + repo_name = user_log.repository.repo_name + return link_to(_('Pull request #%s') % pull_request_id, + url('pullrequest_show', repo_name=repo_name, + pull_request_id=pull_request_id)) + + # action : translated str, callback(extractor), icon + action_map = { + 'user_deleted_repo': (_('[deleted] repository'), + None, 'database_delete.png'), + 'user_created_repo': (_('[created] repository'), + None, 'database_add.png'), + 'user_created_fork': (_('[created] repository as fork'), + None, 'arrow_divide.png'), + 'user_forked_repo': (_('[forked] repository'), + get_fork_name, 'arrow_divide.png'), + 'user_updated_repo': (_('[updated] repository'), + None, 'database_edit.png'), + 'admin_deleted_repo': (_('[delete] repository'), + None, 'database_delete.png'), + 'admin_created_repo': (_('[created] repository'), + None, 'database_add.png'), + 'admin_forked_repo': (_('[forked] repository'), + None, 'arrow_divide.png'), + 'admin_updated_repo': (_('[updated] repository'), + None, 'database_edit.png'), + 'admin_created_user': (_('[created] user'), + get_user_name, 'user_add.png'), + 'admin_updated_user': (_('[updated] user'), + get_user_name, 'user_edit.png'), + 'admin_created_users_group': (_('[created] users group'), + get_users_group, 'group_add.png'), + 'admin_updated_users_group': (_('[updated] users group'), + get_users_group, 'group_edit.png'), + 'user_commented_revision': (_('[commented] on revision in repository'), + get_cs_links, 'comment_add.png'), + 'user_commented_pull_request': (_('[commented] on pull request for'), + get_pull_request, 'comment_add.png'), + 'user_closed_pull_request': (_('[closed] pull request for'), + get_pull_request, 'tick.png'), + 'push': (_('[pushed] into'), + get_cs_links, 'script_add.png'), + 'push_local': (_('[committed via RhodeCode] into repository'), + get_cs_links, 'script_edit.png'), + 'push_remote': (_('[pulled from remote] into repository'), + get_cs_links, 'connect.png'), + 'pull': (_('[pulled] from'), + None, 'down_16.png'), + 'started_following_repo': (_('[started following] repository'), + None, 'heart_add.png'), + 'stopped_following_repo': (_('[stopped following] repository'), + None, 'heart_delete.png'), + } action_str = action_map.get(action, action) if feed: @@ -556,36 +682,21 @@ def action_parser(user_log, feed=False): if callable(action_str[1]): action_params_func = action_str[1] - return [literal(action), action_params_func] - + def action_parser_icon(): + action = user_log.action + action_params = None + x = action.split(':') -def action_parser_icon(user_log): - action = user_log.action - action_params = None - x = action.split(':') - - if len(x) > 1: - action, action_params = x + if len(x) > 1: + action, action_params = x - tmpl = """%s""" - map = {'user_deleted_repo':'database_delete.png', - 'user_created_repo':'database_add.png', - 'user_created_fork':'arrow_divide.png', - 'user_forked_repo':'arrow_divide.png', - 'user_updated_repo':'database_edit.png', - 'admin_deleted_repo':'database_delete.png', - 'admin_created_repo':'database_add.png', - 'admin_forked_repo':'arrow_divide.png', - 'admin_updated_repo':'database_edit.png', - 'push':'script_add.png', - 'push_local':'script_edit.png', - 'push_remote':'connect.png', - 'pull':'down_16.png', - 'started_following_repo':'heart_add.png', - 'stopped_following_repo':'heart_delete.png', - } - return literal(tmpl % ((url('/images/icons/')), - map.get(action, action), action)) + tmpl = """%s""" + ico = action_map.get(action, ['', '', ''])[2] + return literal(tmpl % ((url('/images/icons/')), ico, action)) + + # returned callbacks we need to call to get + return [lambda: literal(action), action_params_func, action_parser_icon] + #============================================================================== @@ -600,6 +711,14 @@ HasRepoPermissionAny, HasRepoPermissionA #============================================================================== def gravatar_url(email_address, size=30): + if(str2bool(config['app_conf'].get('use_gravatar')) and + config['app_conf'].get('alternative_gravatar_url')): + tmpl = config['app_conf'].get('alternative_gravatar_url', '') + tmpl = tmpl.replace('{email}', email_address)\ + .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest())\ + .replace('{size}', str(size)) + return tmpl + if (not str2bool(config['app_conf'].get('use_gravatar')) or not email_address or email_address == 'anonymous@rhodecode.org'): f = lambda a, l: min(l, key=lambda x: abs(x - a)) @@ -875,7 +994,6 @@ def urlify_commit(text_, repository=None return ''.join(links) - # urlify changesets - extrac revisions and make link out of them text_ = urlify_changesets(escaper(text_), repository) @@ -902,7 +1020,8 @@ def urlify_commit(text_, repository=None url = ISSUE_SERVER_LNK.replace('{id}', issue_id) if repository: url = url.replace('{repo}', repository) - + repo_name = repository.split(URL_SEP)[-1] + url = url.replace('{repo_name}', repo_name) return tmpl % { 'pref': pref, 'cls': 'issue-tracker-link', @@ -939,3 +1058,15 @@ def rst_w_mentions(source): """ return literal('
%s
' % MarkupRenderer.rst_with_mentions(source)) + + +def changeset_status(repo, revision): + return ChangesetStatusModel().get_status(repo, revision) + + +def changeset_status_lbl(changeset_status): + return dict(ChangesetStatus.STATUSES).get(changeset_status) + + +def get_permission_name(key): + return dict(Permission.PERMS).get(key) diff --git a/rhodecode/lib/hooks.py b/rhodecode/lib/hooks.py --- a/rhodecode/lib/hooks.py +++ b/rhodecode/lib/hooks.py @@ -24,13 +24,19 @@ # along with this program. If not, see . import os import sys +import binascii +from inspect import isfunction from mercurial.scmutil import revrange from mercurial.node import nullrev -from rhodecode import EXTENSIONS + from rhodecode.lib import helpers as h from rhodecode.lib.utils import action_logger -from inspect import isfunction +from rhodecode.lib.vcs.backends.base import EmptyChangeset +from rhodecode.lib.compat import json +from rhodecode.model.db import Repository, User +from rhodecode.lib.utils2 import safe_str +from rhodecode.lib.exceptions import HTTPLockedRC def _get_scm_size(alias, root_path): @@ -81,6 +87,60 @@ def repo_size(ui, repo, hooktype=None, * sys.stdout.write(msg) +def pre_push(ui, repo, **kwargs): + # pre push function, currently used to ban pushing when + # repository is locked + try: + rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}")) + except: + rc_extras = {} + extras = dict(repo.ui.configitems('rhodecode_extras')) + + if 'username' in extras: + username = extras['username'] + repository = extras['repository'] + scm = extras['scm'] + locked_by = extras['locked_by'] + elif 'username' in rc_extras: + username = rc_extras['username'] + repository = rc_extras['repository'] + scm = rc_extras['scm'] + locked_by = rc_extras['locked_by'] + else: + raise Exception('Missing data in repo.ui and os.environ') + + usr = User.get_by_username(username) + if locked_by[0] and usr.user_id != int(locked_by[0]): + locked_by = User.get(locked_by[0]).username + raise HTTPLockedRC(repository, locked_by) + + +def pre_pull(ui, repo, **kwargs): + # pre push function, currently used to ban pushing when + # repository is locked + try: + rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}")) + except: + rc_extras = {} + extras = dict(repo.ui.configitems('rhodecode_extras')) + if 'username' in extras: + username = extras['username'] + repository = extras['repository'] + scm = extras['scm'] + locked_by = extras['locked_by'] + elif 'username' in rc_extras: + username = rc_extras['username'] + repository = rc_extras['repository'] + scm = rc_extras['scm'] + locked_by = rc_extras['locked_by'] + else: + raise Exception('Missing data in repo.ui and os.environ') + + if locked_by[0]: + locked_by = User.get(locked_by[0]).username + raise HTTPLockedRC(repository, locked_by) + + def log_pull_action(ui, repo, **kwargs): """ Logs user last pull action @@ -88,21 +148,40 @@ def log_pull_action(ui, repo, **kwargs): :param ui: :param repo: """ - + try: + rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}")) + except: + rc_extras = {} extras = dict(repo.ui.configitems('rhodecode_extras')) - username = extras['username'] - repository = extras['repository'] - scm = extras['scm'] + if 'username' in extras: + username = extras['username'] + repository = extras['repository'] + scm = extras['scm'] + make_lock = extras['make_lock'] + elif 'username' in rc_extras: + username = rc_extras['username'] + repository = rc_extras['repository'] + scm = rc_extras['scm'] + make_lock = rc_extras['make_lock'] + else: + raise Exception('Missing data in repo.ui and os.environ') + user = User.get_by_username(username) action = 'pull' - - action_logger(username, action, repository, extras['ip'], commit=True) + action_logger(user, action, repository, extras['ip'], commit=True) # extension hook call + from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'PULL_HOOK', None) if isfunction(callback): kw = {} kw.update(extras) callback(**kw) + + if make_lock is True: + Repository.lock(Repository.get_by_repo_name(repository), user.user_id) + #msg = 'Made lock on repo `%s`' % repository + #sys.stdout.write(msg) + return 0 @@ -114,11 +193,26 @@ def log_push_action(ui, repo, **kwargs): :param repo: repo object containing the `ui` object """ + try: + rc_extras = json.loads(os.environ.get('RC_SCM_DATA', "{}")) + except: + rc_extras = {} + extras = dict(repo.ui.configitems('rhodecode_extras')) - username = extras['username'] - repository = extras['repository'] - action = extras['action'] + ':%s' - scm = extras['scm'] + if 'username' in extras: + username = extras['username'] + repository = extras['repository'] + scm = extras['scm'] + make_lock = extras['make_lock'] + elif 'username' in rc_extras: + username = rc_extras['username'] + repository = rc_extras['repository'] + scm = rc_extras['scm'] + make_lock = rc_extras['make_lock'] + else: + raise Exception('Missing data in repo.ui and os.environ') + + action = 'push' + ':%s' if scm == 'hg': node = kwargs['node'] @@ -134,21 +228,30 @@ def log_push_action(ui, repo, **kwargs): return (len(repo) - 1, 0) stop, start = get_revs(repo, [node + ':']) - - revs = (str(repo[r]) for r in xrange(start, stop + 1)) + h = binascii.hexlify + revs = [h(repo[r].node()) for r in xrange(start, stop + 1)] elif scm == 'git': - revs = [] + revs = kwargs.get('_git_revs', []) + if '_git_revs' in kwargs: + kwargs.pop('_git_revs') action = action % ','.join(revs) action_logger(username, action, repository, extras['ip'], commit=True) # extension hook call + from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'PUSH_HOOK', None) if isfunction(callback): kw = {'pushed_revs': revs} kw.update(extras) callback(**kw) + + if make_lock is False: + Repository.unlock(Repository.get_by_repo_name(repository)) + msg = 'Released lock on repo `%s`\n' % repository + sys.stdout.write(msg) + return 0 @@ -178,7 +281,7 @@ def log_create_repository(repository_dic 'repo_name' """ - + from rhodecode import EXTENSIONS callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None) if isfunction(callback): kw = {} @@ -188,3 +291,88 @@ def log_create_repository(repository_dic return callback(**kw) return 0 + +handle_git_pre_receive = (lambda repo_path, revs, env: + handle_git_receive(repo_path, revs, env, hook_type='pre')) +handle_git_post_receive = (lambda repo_path, revs, env: + handle_git_receive(repo_path, revs, env, hook_type='post')) + + +def handle_git_receive(repo_path, revs, env, hook_type='post'): + """ + A really hacky method that is runned by git post-receive hook and logs + an push action together with pushed revisions. It's executed by subprocess + thus needs all info to be able to create a on the fly pylons enviroment, + connect to database and run the logging code. Hacky as sh*t but works. + + :param repo_path: + :type repo_path: + :param revs: + :type revs: + :param env: + :type env: + """ + from paste.deploy import appconfig + from sqlalchemy import engine_from_config + from rhodecode.config.environment import load_environment + from rhodecode.model import init_model + from rhodecode.model.db import RhodeCodeUi + from rhodecode.lib.utils import make_ui + + path, ini_name = os.path.split(env['RHODECODE_CONFIG_FILE']) + conf = appconfig('config:%s' % ini_name, relative_to=path) + load_environment(conf.global_conf, conf.local_conf) + + engine = engine_from_config(conf, 'sqlalchemy.db1.') + init_model(engine) + + baseui = make_ui('db') + # fix if it's not a bare repo + if repo_path.endswith('.git'): + repo_path = repo_path[:-4] + repo = Repository.get_by_full_path(repo_path) + _hooks = dict(baseui.configitems('hooks')) or {} + + extras = json.loads(env['RHODECODE_EXTRAS']) + for k, v in extras.items(): + baseui.setconfig('rhodecode_extras', k, v) + repo = repo.scm_instance + repo.ui = baseui + + if hook_type == 'pre': + pre_push(baseui, repo) + + # if push hook is enabled via web interface + elif hook_type == 'post' and _hooks.get(RhodeCodeUi.HOOK_PUSH): + + rev_data = [] + for l in revs: + old_rev, new_rev, ref = l.split(' ') + _ref_data = ref.split('/') + if _ref_data[1] in ['tags', 'heads']: + rev_data.append({'old_rev': old_rev, + 'new_rev': new_rev, + 'ref': ref, + 'type': _ref_data[1], + 'name': _ref_data[2].strip()}) + + git_revs = [] + for push_ref in rev_data: + _type = push_ref['type'] + if _type == 'heads': + if push_ref['old_rev'] == EmptyChangeset().raw_id: + cmd = "for-each-ref --format='%(refname)' 'refs/heads/*'" + heads = repo.run_git_command(cmd)[0] + heads = heads.replace(push_ref['ref'], '') + heads = ' '.join(map(lambda c: c.strip('\n').strip(), + heads.splitlines())) + cmd = (('log %(new_rev)s' % push_ref) + + ' --reverse --pretty=format:"%H" --not ' + heads) + else: + cmd = (('log %(old_rev)s..%(new_rev)s' % push_ref) + + ' --reverse --pretty=format:"%H"') + git_revs += repo.run_git_command(cmd)[0].splitlines() + elif _type == 'tags': + git_revs += [push_ref['name']] + + log_push_action(baseui, repo, _git_revs=git_revs) diff --git a/rhodecode/lib/indexers/__init__.py b/rhodecode/lib/indexers/__init__.py --- a/rhodecode/lib/indexers/__init__.py +++ b/rhodecode/lib/indexers/__init__.py @@ -35,12 +35,12 @@ from string import strip from shutil import rmtree from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter -from whoosh.fields import TEXT, ID, STORED, Schema, FieldType +from whoosh.fields import TEXT, ID, STORED, NUMERIC, BOOLEAN, Schema, FieldType from whoosh.index import create_in, open_dir from whoosh.formats import Characters from whoosh.highlight import highlight, HtmlFormatter, ContextFragmenter -from webhelpers.html.builder import escape +from webhelpers.html.builder import escape, literal from sqlalchemy import engine_from_config from rhodecode.model import init_model @@ -51,12 +51,14 @@ from rhodecode.lib.utils2 import LazyPro from rhodecode.lib.utils import BasePasterCommand, Command, add_cache,\ load_rcextensions +log = logging.getLogger(__name__) + # CUSTOM ANALYZER wordsplit + lowercase filter ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter() - #INDEX SCHEMA DEFINITION SCHEMA = Schema( + fileid=ID(unique=True), owner=TEXT(), repository=TEXT(stored=True), path=TEXT(stored=True), @@ -70,6 +72,23 @@ IDX_NAME = 'HG_INDEX' FORMATTER = HtmlFormatter('span', between='\n...\n') FRAGMENTER = ContextFragmenter(200) +CHGSETS_SCHEMA = Schema( + raw_id=ID(unique=True, stored=True), + date=NUMERIC(stored=True), + last=BOOLEAN(), + owner=TEXT(), + repository=ID(unique=True, stored=True), + author=TEXT(stored=True), + message=FieldType(format=Characters(), analyzer=ANALYZER, + scorable=True, stored=True), + parents=TEXT(), + added=TEXT(), + removed=TEXT(), + changed=TEXT(), +) + +CHGSET_IDX_NAME = 'CHGSET_INDEX' + class MakeIndex(BasePasterCommand): @@ -93,6 +112,8 @@ class MakeIndex(BasePasterCommand): if self.options.repo_location else RepoModel().repos_path repo_list = map(strip, self.options.repo_list.split(',')) \ if self.options.repo_list else None + repo_update_list = map(strip, self.options.repo_update_list.split(',')) \ + if self.options.repo_update_list else None load_rcextensions(config['here']) #====================================================================== # WHOOSH DAEMON @@ -103,7 +124,8 @@ class MakeIndex(BasePasterCommand): l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock')) WhooshIndexingDaemon(index_location=index_location, repo_location=repo_location, - repo_list=repo_list,)\ + repo_list=repo_list, + repo_update_list=repo_update_list)\ .run(full_index=self.options.full_index) l.release() except LockHeld: @@ -119,7 +141,14 @@ class MakeIndex(BasePasterCommand): action='store', dest='repo_list', help="Specifies a comma separated list of repositores " - "to build index on OPTIONAL", + "to build index on. If not given all repositories " + "are scanned for indexing. OPTIONAL", + ) + self.parser.add_option('--update-only', + action='store', + dest='repo_update_list', + help="Specifies a comma separated list of repositores " + "to re-build index on. OPTIONAL", ) self.parser.add_option('-f', action='store_true', @@ -129,13 +158,15 @@ class MakeIndex(BasePasterCommand): default=False) -class ResultWrapper(object): - def __init__(self, search_type, searcher, matcher, highlight_items): +class WhooshResultWrapper(object): + def __init__(self, search_type, searcher, matcher, highlight_items, + repo_location): self.search_type = search_type self.searcher = searcher self.matcher = matcher self.highlight_items = highlight_items self.fragment_size = 200 + self.repo_location = repo_location @LazyProperty def doc_ids(self): @@ -178,13 +209,25 @@ class ResultWrapper(object): def get_full_content(self, docid): res = self.searcher.stored_fields(docid[0]) - f_path = res['path'][res['path'].find(res['repository']) \ - + len(res['repository']):].lstrip('/') + log.debug('result: %s' % res) + if self.search_type == 'content': + full_repo_path = jn(self.repo_location, res['repository']) + f_path = res['path'].split(full_repo_path)[-1] + f_path = f_path.lstrip(os.sep) + content_short = self.get_short_content(res, docid[1]) + res.update({'content_short': content_short, + 'content_short_hl': self.highlight(content_short), + 'f_path': f_path + }) + elif self.search_type == 'path': + full_repo_path = jn(self.repo_location, res['repository']) + f_path = res['path'].split(full_repo_path)[-1] + f_path = f_path.lstrip(os.sep) + res.update({'f_path': f_path}) + elif self.search_type == 'message': + res.update({'message_hl': self.highlight(res['message'])}) - content_short = self.get_short_content(res, docid[1]) - res.update({'content_short': content_short, - 'content_short_hl': self.highlight(content_short), - 'f_path': f_path}) + log.debug('result: %s' % res) return res @@ -202,22 +245,23 @@ class ResultWrapper(object): :param size: """ memory = [(0, 0)] - for span in self.matcher.spans(): - start = span.startchar or 0 - end = span.endchar or 0 - start_offseted = max(0, start - self.fragment_size) - end_offseted = end + self.fragment_size + if self.matcher.supports('positions'): + for span in self.matcher.spans(): + start = span.startchar or 0 + end = span.endchar or 0 + start_offseted = max(0, start - self.fragment_size) + end_offseted = end + self.fragment_size - if start_offseted < memory[-1][1]: - start_offseted = memory[-1][1] - memory.append((start_offseted, end_offseted,)) - yield (start_offseted, end_offseted,) + if start_offseted < memory[-1][1]: + start_offseted = memory[-1][1] + memory.append((start_offseted, end_offseted,)) + yield (start_offseted, end_offseted,) def highlight(self, content, top=5): - if self.search_type != 'content': + if self.search_type not in ['content', 'message']: return '' hl = highlight( - text=escape(content), + text=content, terms=self.highlight_items, analyzer=ANALYZER, fragmenter=FRAGMENTER, diff --git a/rhodecode/lib/indexers/daemon.py b/rhodecode/lib/indexers/daemon.py --- a/rhodecode/lib/indexers/daemon.py +++ b/rhodecode/lib/indexers/daemon.py @@ -22,6 +22,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import with_statement import os import sys @@ -41,23 +42,27 @@ sys.path.append(project_path) from rhodecode.config.conf import INDEX_EXTENSIONS from rhodecode.model.scm import ScmModel from rhodecode.lib.utils2 import safe_unicode -from rhodecode.lib.indexers import SCHEMA, IDX_NAME +from rhodecode.lib.indexers import SCHEMA, IDX_NAME, CHGSETS_SCHEMA, \ + CHGSET_IDX_NAME from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \ NodeDoesNotExistError -from whoosh.index import create_in, open_dir +from whoosh.index import create_in, open_dir, exists_in +from whoosh.query import * +from whoosh.qparser import QueryParser log = logging.getLogger('whoosh_indexer') class WhooshIndexingDaemon(object): """ - Daemon for atomic jobs + Daemon for atomic indexing jobs """ def __init__(self, indexname=IDX_NAME, index_location=None, - repo_location=None, sa=None, repo_list=None): + repo_location=None, sa=None, repo_list=None, + repo_update_list=None): self.indexname = indexname self.index_location = index_location @@ -70,20 +75,37 @@ class WhooshIndexingDaemon(object): self.repo_paths = ScmModel(sa).repo_scan(self.repo_location) + #filter repo list if repo_list: - filtered_repo_paths = {} + self.filtered_repo_paths = {} for repo_name, repo in self.repo_paths.items(): if repo_name in repo_list: - filtered_repo_paths[repo_name] = repo + self.filtered_repo_paths[repo_name] = repo + + self.repo_paths = self.filtered_repo_paths - self.repo_paths = filtered_repo_paths + #filter update repo list + self.filtered_repo_update_paths = {} + if repo_update_list: + self.filtered_repo_update_paths = {} + for repo_name, repo in self.repo_paths.items(): + if repo_name in repo_update_list: + self.filtered_repo_update_paths[repo_name] = repo + self.repo_paths = self.filtered_repo_update_paths - self.initial = False + self.initial = True if not os.path.isdir(self.index_location): os.makedirs(self.index_location) log.info('Cannot run incremental index since it does not' ' yet exist running full build') - self.initial = True + elif not exists_in(self.index_location, IDX_NAME): + log.info('Running full index build as the file content' + ' index does not exist') + elif not exists_in(self.index_location, CHGSET_IDX_NAME): + log.info('Running full index build as the changeset' + ' index does not exist') + else: + self.initial = False def get_paths(self, repo): """ @@ -93,11 +115,11 @@ class WhooshIndexingDaemon(object): index_paths_ = set() try: tip = repo.get_changeset('tip') - for topnode, dirs, files in tip.walk('/'): + for _topnode, _dirs, files in tip.walk('/'): for f in files: index_paths_.add(jn(repo.path, f.path)) - except RepositoryError, e: + except RepositoryError: log.debug(traceback.format_exc()) pass return index_paths_ @@ -135,45 +157,136 @@ class WhooshIndexingDaemon(object): u_content = u'' indexed += 1 + p = safe_unicode(path) writer.add_document( + fileid=p, owner=unicode(repo.contact), repository=safe_unicode(repo_name), - path=safe_unicode(path), + path=p, content=u_content, modtime=self.get_node_mtime(node), extension=node.extension ) return indexed, indexed_w_content - def build_index(self): - if os.path.exists(self.index_location): - log.debug('removing previous index') - rmtree(self.index_location) + def index_changesets(self, writer, repo_name, repo, start_rev=None): + """ + Add all changeset in the vcs repo starting at start_rev + to the index writer + + :param writer: the whoosh index writer to add to + :param repo_name: name of the repository from whence the + changeset originates including the repository group + :param repo: the vcs repository instance to index changesets for, + the presumption is the repo has changesets to index + :param start_rev=None: the full sha id to start indexing from + if start_rev is None then index from the first changeset in + the repo + """ + + if start_rev is None: + start_rev = repo[0].raw_id + + log.debug('indexing changesets in %s starting at rev: %s' % + (repo_name, start_rev)) - if not os.path.exists(self.index_location): - os.mkdir(self.index_location) + indexed = 0 + for cs in repo.get_changesets(start=start_rev): + log.debug(' >> %s' % cs) + writer.add_document( + raw_id=unicode(cs.raw_id), + owner=unicode(repo.contact), + date=cs._timestamp, + repository=safe_unicode(repo_name), + author=cs.author, + message=cs.message, + last=cs.last, + added=u' '.join([safe_unicode(node.path) for node in cs.added]).lower(), + removed=u' '.join([safe_unicode(node.path) for node in cs.removed]).lower(), + changed=u' '.join([safe_unicode(node.path) for node in cs.changed]).lower(), + parents=u' '.join([cs.raw_id for cs in cs.parents]), + ) + indexed += 1 + + log.debug('indexed %d changesets for repo %s' % (indexed, repo_name)) + return indexed + + def index_files(self, file_idx_writer, repo_name, repo): + """ + Index files for given repo_name + + :param file_idx_writer: the whoosh index writer to add to + :param repo_name: name of the repository we're indexing + :param repo: instance of vcs repo + """ + i_cnt = iwc_cnt = 0 + log.debug('building index for [%s]' % repo.path) + for idx_path in self.get_paths(repo): + i, iwc = self.add_doc(file_idx_writer, idx_path, repo, repo_name) + i_cnt += i + iwc_cnt += iwc - idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME) - writer = idx.writer() - log.debug('BUILDIN INDEX FOR EXTENSIONS %s' % INDEX_EXTENSIONS) - for repo_name, repo in self.repo_paths.items(): - log.debug('building index @ %s' % repo.path) - i_cnt = iwc_cnt = 0 - for idx_path in self.get_paths(repo): - i, iwc = self.add_doc(writer, idx_path, repo, repo_name) - i_cnt += i - iwc_cnt += iwc - log.debug('added %s files %s with content for repo %s' % ( - i_cnt + iwc_cnt, iwc_cnt, repo.path) - ) + log.debug('added %s files %s with content for repo %s' % + (i_cnt + iwc_cnt, iwc_cnt, repo.path)) + return i_cnt, iwc_cnt + + def update_changeset_index(self): + idx = open_dir(self.index_location, indexname=CHGSET_IDX_NAME) + + with idx.searcher() as searcher: + writer = idx.writer() + writer_is_dirty = False + try: + indexed_total = 0 + for repo_name, repo in self.repo_paths.items(): + # skip indexing if there aren't any revs in the repo + num_of_revs = len(repo) + if num_of_revs < 1: + continue + + qp = QueryParser('repository', schema=CHGSETS_SCHEMA) + q = qp.parse(u"last:t AND %s" % repo_name) + + results = searcher.search(q) + + # default to scanning the entire repo + last_rev = 0 + start_id = None - log.debug('>> COMMITING CHANGES <<') - writer.commit(merge=True) - log.debug('>>> FINISHED BUILDING INDEX <<<') + if len(results) > 0: + # assuming that there is only one result, if not this + # may require a full re-index. + start_id = results[0]['raw_id'] + last_rev = repo.get_changeset(revision=start_id).revision + + # there are new changesets to index or a new repo to index + if last_rev == 0 or num_of_revs > last_rev + 1: + # delete the docs in the index for the previous + # last changeset(s) + for hit in results: + q = qp.parse(u"last:t AND %s AND raw_id:%s" % + (repo_name, hit['raw_id'])) + writer.delete_by_query(q) - def update_index(self): - log.debug('STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s' % - INDEX_EXTENSIONS) + # index from the previous last changeset + all new ones + indexed_total += self.index_changesets(writer, + repo_name, repo, start_id) + writer_is_dirty = True + log.debug('indexed %s changesets for repo %s' % ( + indexed_total, repo_name) + ) + finally: + if writer_is_dirty: + log.debug('>> COMMITING CHANGES TO CHANGESET INDEX<<') + writer.commit(merge=True) + log.debug('>> COMMITTED CHANGES TO CHANGESET INDEX<<') + else: + writer.cancel + log.debug('>> NOTHING TO COMMIT<<') + + def update_file_index(self): + log.debug((u'STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s ' + 'AND REPOS %s') % (INDEX_EXTENSIONS, self.repo_paths.keys())) idx = open_dir(self.index_location, indexname=self.indexname) # The set of all paths in the index @@ -181,57 +294,120 @@ class WhooshIndexingDaemon(object): # The set of all paths we need to re-index to_index = set() - reader = idx.reader() writer = idx.writer() + writer_is_dirty = False + try: + with idx.reader() as reader: + + # Loop over the stored fields in the index + for fields in reader.all_stored_fields(): + indexed_path = fields['path'] + indexed_repo_path = fields['repository'] + indexed_paths.add(indexed_path) + + if not indexed_repo_path in self.filtered_repo_update_paths: + continue - # Loop over the stored fields in the index - for fields in reader.all_stored_fields(): - indexed_path = fields['path'] - indexed_paths.add(indexed_path) + repo = self.repo_paths[indexed_repo_path] + + try: + node = self.get_node(repo, indexed_path) + # Check if this file was changed since it was indexed + indexed_time = fields['modtime'] + mtime = self.get_node_mtime(node) + if mtime > indexed_time: + # The file has changed, delete it and add it to + # the list of files to reindex + log.debug( + 'adding to reindex list %s mtime: %s vs %s' % ( + indexed_path, mtime, indexed_time) + ) + writer.delete_by_term('fileid', indexed_path) + writer_is_dirty = True - repo = self.repo_paths[fields['repository']] + to_index.add(indexed_path) + except (ChangesetError, NodeDoesNotExistError): + # This file was deleted since it was indexed + log.debug('removing from index %s' % indexed_path) + writer.delete_by_term('path', indexed_path) + writer_is_dirty = True - try: - node = self.get_node(repo, indexed_path) - except (ChangesetError, NodeDoesNotExistError): - # This file was deleted since it was indexed - log.debug('removing from index %s' % indexed_path) - writer.delete_by_term('path', indexed_path) + # Loop over the files in the filesystem + # Assume we have a function that gathers the filenames of the + # documents to be indexed + ri_cnt_total = 0 # indexed + riwc_cnt_total = 0 # indexed with content + for repo_name, repo in self.repo_paths.items(): + # skip indexing if there aren't any revisions + if len(repo) < 1: + continue + ri_cnt = 0 # indexed + riwc_cnt = 0 # indexed with content + for path in self.get_paths(repo): + path = safe_unicode(path) + if path in to_index or path not in indexed_paths: + # This is either a file that's changed, or a new file + # that wasn't indexed before. So index it! + i, iwc = self.add_doc(writer, path, repo, repo_name) + writer_is_dirty = True + log.debug('re indexing %s' % path) + ri_cnt += i + ri_cnt_total += 1 + riwc_cnt += iwc + riwc_cnt_total += iwc + log.debug('added %s files %s with content for repo %s' % ( + ri_cnt + riwc_cnt, riwc_cnt, repo.path) + ) + log.debug('indexed %s files in total and %s with content' % ( + ri_cnt_total, riwc_cnt_total) + ) + finally: + if writer_is_dirty: + log.debug('>> COMMITING CHANGES <<') + writer.commit(merge=True) + log.debug('>>> FINISHED REBUILDING INDEX <<<') else: - # Check if this file was changed since it was indexed - indexed_time = fields['modtime'] - mtime = self.get_node_mtime(node) - if mtime > indexed_time: - # The file has changed, delete it and add it to the list of - # files to reindex - log.debug('adding to reindex list %s' % indexed_path) - writer.delete_by_term('path', indexed_path) - to_index.add(indexed_path) + log.debug('>> NOTHING TO COMMIT<<') + writer.cancel() + + def build_indexes(self): + if os.path.exists(self.index_location): + log.debug('removing previous index') + rmtree(self.index_location) - # Loop over the files in the filesystem - # Assume we have a function that gathers the filenames of the - # documents to be indexed - ri_cnt = riwc_cnt = 0 + if not os.path.exists(self.index_location): + os.mkdir(self.index_location) + + chgset_idx = create_in(self.index_location, CHGSETS_SCHEMA, + indexname=CHGSET_IDX_NAME) + chgset_idx_writer = chgset_idx.writer() + + file_idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME) + file_idx_writer = file_idx.writer() + log.debug('BUILDING INDEX FOR EXTENSIONS %s ' + 'AND REPOS %s' % (INDEX_EXTENSIONS, self.repo_paths.keys())) + for repo_name, repo in self.repo_paths.items(): - for path in self.get_paths(repo): - if path in to_index or path not in indexed_paths: - # This is either a file that's changed, or a new file - # that wasn't indexed before. So index it! - i, iwc = self.add_doc(writer, path, repo, repo_name) - log.debug('re indexing %s' % path) - ri_cnt += i - riwc_cnt += iwc - log.debug('added %s files %s with content for repo %s' % ( - ri_cnt + riwc_cnt, riwc_cnt, repo.path) - ) + # skip indexing if there aren't any revisions + if len(repo) < 1: + continue + + self.index_files(file_idx_writer, repo_name, repo) + self.index_changesets(chgset_idx_writer, repo_name, repo) + log.debug('>> COMMITING CHANGES <<') - writer.commit(merge=True) - log.debug('>>> FINISHED REBUILDING INDEX <<<') + file_idx_writer.commit(merge=True) + chgset_idx_writer.commit(merge=True) + log.debug('>>> FINISHED BUILDING INDEX <<<') + + def update_indexes(self): + self.update_file_index() + self.update_changeset_index() def run(self, full_index=False): """Run daemon""" if full_index or self.initial: - self.build_index() + self.build_indexes() else: - self.update_index() + self.update_indexes() diff --git a/rhodecode/lib/markup_renderer.py b/rhodecode/lib/markup_renderer.py --- a/rhodecode/lib/markup_renderer.py +++ b/rhodecode/lib/markup_renderer.py @@ -26,6 +26,7 @@ import re import logging +import traceback from rhodecode.lib.utils2 import safe_unicode, MENTIONS_REGEX @@ -93,7 +94,7 @@ class MarkupRenderer(object): return '
' + source.replace("\n", '
') @classmethod - def markdown(cls, source): + def markdown(cls, source, safe=True): source = safe_unicode(source) try: import markdown as __markdown @@ -101,9 +102,15 @@ class MarkupRenderer(object): except ImportError: log.warning('Install markdown to use this function') return cls.plain(source) + except Exception: + log.error(traceback.format_exc()) + if safe: + return source + else: + raise @classmethod - def rst(cls, source): + def rst(cls, source, safe=True): source = safe_unicode(source) try: from docutils.core import publish_parts @@ -125,6 +132,12 @@ class MarkupRenderer(object): except ImportError: log.warning('Install docutils to use this function') return cls.plain(source) + except Exception: + log.error(traceback.format_exc()) + if safe: + return source + else: + raise @classmethod def rst_with_mentions(cls, source): diff --git a/rhodecode/lib/middleware/https_fixup.py b/rhodecode/lib/middleware/https_fixup.py --- a/rhodecode/lib/middleware/https_fixup.py +++ b/rhodecode/lib/middleware/https_fixup.py @@ -42,21 +42,20 @@ class HttpsFixup(object): middleware you should set this header inside your proxy ie. nginx, apache etc. """ + # DETECT PROTOCOL ! + if 'HTTP_X_URL_SCHEME' in environ: + proto = environ.get('HTTP_X_URL_SCHEME') + elif 'HTTP_X_FORWARDED_SCHEME' in environ: + proto = environ.get('HTTP_X_FORWARDED_SCHEME') + elif 'HTTP_X_FORWARDED_PROTO' in environ: + proto = environ.get('HTTP_X_FORWARDED_PROTO') + else: + proto = 'http' + org_proto = proto + # if we have force, just override if str2bool(self.config.get('force_https')): proto = 'https' - else: - if 'HTTP_X_URL_SCHEME' in environ: - proto = environ.get('HTTP_X_URL_SCHEME') - elif 'HTTP_X_FORWARDED_SCHEME' in environ: - proto = environ.get('HTTP_X_FORWARDED_SCHEME') - elif 'HTTP_X_FORWARDED_PROTO' in environ: - proto = environ.get('HTTP_X_FORWARDED_PROTO') - else: - proto = 'http' - if proto == 'https': - environ['wsgi.url_scheme'] = proto - else: - environ['wsgi.url_scheme'] = 'http' - return None + environ['wsgi.url_scheme'] = proto + environ['wsgi._org_proto'] = org_proto diff --git a/rhodecode/lib/middleware/pygrack.py b/rhodecode/lib/middleware/pygrack.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/middleware/pygrack.py @@ -0,0 +1,200 @@ +import os +import socket +import logging +import subprocess + +from webob import Request, Response, exc + +from rhodecode.lib import subprocessio + +log = logging.getLogger(__name__) + + +class FileWrapper(object): + + def __init__(self, fd, content_length): + self.fd = fd + self.content_length = content_length + self.remain = content_length + + def read(self, size): + if size <= self.remain: + try: + data = self.fd.read(size) + except socket.error: + raise IOError(self) + self.remain -= size + elif self.remain: + data = self.fd.read(self.remain) + self.remain = 0 + else: + data = None + return data + + def __repr__(self): + return '' % ( + self.fd, self.content_length, self.content_length - self.remain + ) + + +class GitRepository(object): + git_folder_signature = set(['config', 'head', 'info', 'objects', 'refs']) + commands = ['git-upload-pack', 'git-receive-pack'] + + def __init__(self, repo_name, content_path, extras): + files = set([f.lower() for f in os.listdir(content_path)]) + if not (self.git_folder_signature.intersection(files) + == self.git_folder_signature): + raise OSError('%s missing git signature' % content_path) + self.content_path = content_path + self.valid_accepts = ['application/x-%s-result' % + c for c in self.commands] + self.repo_name = repo_name + self.extras = extras + + def _get_fixedpath(self, path): + """ + Small fix for repo_path + + :param path: + :type path: + """ + return path.split(self.repo_name, 1)[-1].strip('/') + + def inforefs(self, request, environ): + """ + WSGI Response producer for HTTP GET Git Smart + HTTP /info/refs request. + """ + + git_command = request.GET.get('service') + if git_command not in self.commands: + log.debug('command %s not allowed' % git_command) + return exc.HTTPMethodNotAllowed() + + # note to self: + # please, resist the urge to add '\n' to git capture and increment + # line count by 1. + # The code in Git client not only does NOT need '\n', but actually + # blows up if you sprinkle "flush" (0000) as "0001\n". + # It reads binary, per number of bytes specified. + # if you do add '\n' as part of data, count it. + server_advert = '# service=%s' % git_command + packet_len = str(hex(len(server_advert) + 4)[2:].rjust(4, '0')).lower() + try: + out = subprocessio.SubprocessIOChunker( + r'git %s --stateless-rpc --advertise-refs "%s"' % ( + git_command[4:], self.content_path), + starting_values=[ + packet_len + server_advert + '0000' + ] + ) + except EnvironmentError, e: + log.exception(e) + raise exc.HTTPExpectationFailed() + resp = Response() + resp.content_type = 'application/x-%s-advertisement' % str(git_command) + resp.charset = None + resp.app_iter = out + return resp + + def backend(self, request, environ): + """ + WSGI Response producer for HTTP POST Git Smart HTTP requests. + Reads commands and data from HTTP POST's body. + returns an iterator obj with contents of git command's + response to stdout + """ + git_command = self._get_fixedpath(request.path_info) + if git_command not in self.commands: + log.debug('command %s not allowed' % git_command) + return exc.HTTPMethodNotAllowed() + + if 'CONTENT_LENGTH' in environ: + inputstream = FileWrapper(environ['wsgi.input'], + request.content_length) + else: + inputstream = environ['wsgi.input'] + + try: + gitenv = os.environ + from rhodecode import CONFIG + from rhodecode.lib.compat import json + gitenv['RHODECODE_EXTRAS'] = json.dumps(self.extras) + # forget all configs + gitenv['GIT_CONFIG_NOGLOBAL'] = '1' + # we need current .ini file used to later initialize rhodecode + # env and connect to db + gitenv['RHODECODE_CONFIG_FILE'] = CONFIG['__file__'] + opts = dict( + env=gitenv, + cwd=os.getcwd() + ) + out = subprocessio.SubprocessIOChunker( + r'git %s --stateless-rpc "%s"' % (git_command[4:], + self.content_path), + inputstream=inputstream, + **opts + ) + except EnvironmentError, e: + log.exception(e) + raise exc.HTTPExpectationFailed() + + if git_command in [u'git-receive-pack']: + # updating refs manually after each push. + # Needed for pre-1.7.0.4 git clients using regular HTTP mode. + subprocess.call(u'git --git-dir "%s" ' + 'update-server-info' % self.content_path, + shell=True) + + resp = Response() + resp.content_type = 'application/x-%s-result' % git_command.encode('utf8') + resp.charset = None + resp.app_iter = out + return resp + + def __call__(self, environ, start_response): + request = Request(environ) + _path = self._get_fixedpath(request.path_info) + if _path.startswith('info/refs'): + app = self.inforefs + elif [a for a in self.valid_accepts if a in request.accept]: + app = self.backend + try: + resp = app(request, environ) + except exc.HTTPException, e: + resp = e + log.exception(e) + except Exception, e: + log.exception(e) + resp = exc.HTTPInternalServerError() + return resp(environ, start_response) + + +class GitDirectory(object): + + def __init__(self, repo_root, repo_name, extras): + repo_location = os.path.join(repo_root, repo_name) + if not os.path.isdir(repo_location): + raise OSError(repo_location) + + self.content_path = repo_location + self.repo_name = repo_name + self.repo_location = repo_location + self.extras = extras + + def __call__(self, environ, start_response): + content_path = self.content_path + try: + app = GitRepository(self.repo_name, content_path, self.extras) + except (AssertionError, OSError): + content_path = os.path.join(content_path, '.git') + if os.path.isdir(content_path): + app = GitRepository(self.repo_name, content_path, self.extras) + else: + return exc.HTTPNotFound()(environ, start_response) + return app(environ, start_response) + + +def make_wsgi_app(repo_name, repo_root, extras): + return GitDirectory(repo_root, repo_name, extras) diff --git a/rhodecode/lib/middleware/simplegit.py b/rhodecode/lib/middleware/simplegit.py --- a/rhodecode/lib/middleware/simplegit.py +++ b/rhodecode/lib/middleware/simplegit.py @@ -30,6 +30,9 @@ import logging import traceback from dulwich import server as dulserver +from dulwich.web import LimitedInputFilter, GunzipFilter +from rhodecode.lib.exceptions import HTTPLockedRC +from rhodecode.lib.hooks import pre_pull class SimpleGitUploadPackHandler(dulserver.UploadPackHandler): @@ -62,22 +65,26 @@ class SimpleGitUploadPackHandler(dulserv dulserver.DEFAULT_HANDLERS = { + #git-ls-remote, git-clone, git-fetch and git-pull 'git-upload-pack': SimpleGitUploadPackHandler, + #git-push 'git-receive-pack': dulserver.ReceivePackHandler, } -from dulwich.repo import Repo -from dulwich.web import make_wsgi_chain +# not used for now until dulwich get's fixed +#from dulwich.repo import Repo +#from dulwich.web import make_wsgi_chain from paste.httpheaders import REMOTE_USER, AUTH_TYPE +from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ + HTTPBadRequest, HTTPNotAcceptable from rhodecode.lib.utils2 import safe_str from rhodecode.lib.base import BaseVCSController from rhodecode.lib.auth import get_container_username from rhodecode.lib.utils import is_valid_repo, make_ui -from rhodecode.model.db import User - -from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError +from rhodecode.lib.compat import json +from rhodecode.model.db import User, RhodeCodeUi log = logging.getLogger(__name__) @@ -97,9 +104,10 @@ def is_git(environ): class SimpleGit(BaseVCSController): def _handle_request(self, environ, start_response): - if not is_git(environ): return self.application(environ, start_response) + if not self._check_ssl(environ, start_response): + return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) ipaddr = self._get_ip_addr(environ) username = None @@ -117,7 +125,7 @@ class SimpleGit(BaseVCSController): return HTTPInternalServerError()(environ, start_response) # quick check if that dir exists... - if is_valid_repo(repo_name, self.basepath) is False: + if is_valid_repo(repo_name, self.basepath, 'git') is False: return HTTPNotFound()(environ, start_response) #====================================================================== @@ -164,27 +172,30 @@ class SimpleGit(BaseVCSController): #============================================================== # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME #============================================================== - if action in ['pull', 'push']: - try: - user = self.__get_user(username) - if user is None or not user.active: - return HTTPForbidden()(environ, start_response) - username = user.username - except: - log.error(traceback.format_exc()) - return HTTPInternalServerError()(environ, - start_response) + try: + user = self.__get_user(username) + if user is None or not user.active: + return HTTPForbidden()(environ, start_response) + username = user.username + except: + log.error(traceback.format_exc()) + return HTTPInternalServerError()(environ, start_response) - #check permissions for this repository - perm = self._check_permission(action, user, repo_name) - if perm is not True: - return HTTPForbidden()(environ, start_response) + #check permissions for this repository + perm = self._check_permission(action, user, repo_name) + if perm is not True: + return HTTPForbidden()(environ, start_response) + + # extras are injected into UI object and later available + # in hooks executed by rhodecode extras = { 'ip': ipaddr, 'username': username, 'action': action, 'repository': repo_name, 'scm': 'git', + 'make_lock': None, + 'locked_by': [None, None] } #=================================================================== @@ -193,10 +204,24 @@ class SimpleGit(BaseVCSController): repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name)) log.debug('Repository path is %s' % repo_path) + # CHECK LOCKING only if it's not ANONYMOUS USER + if username != User.DEFAULT_USER: + log.debug('Checking locking on repository') + (make_lock, + locked, + locked_by) = self._check_locking_state( + environ=environ, action=action, + repo=repo_name, user_id=user.user_id + ) + # store the make_lock for later evaluation in hooks + extras.update({'make_lock': make_lock, + 'locked_by': locked_by}) + # set the environ variables for this request + os.environ['RC_SCM_DATA'] = json.dumps(extras) + log.debug('HOOKS extras is %s' % extras) baseui = make_ui('db') self.__inject_extras(repo_path, baseui, extras) - try: # invalidate cache on push if action == 'push': @@ -204,24 +229,31 @@ class SimpleGit(BaseVCSController): self._handle_githooks(repo_name, action, baseui, environ) log.info('%s action on GIT repo "%s"' % (action, repo_name)) - app = self.__make_app(repo_name, repo_path) + app = self.__make_app(repo_name, repo_path, extras) return app(environ, start_response) + except HTTPLockedRC, e: + log.debug('Repositry LOCKED ret code 423!') + return e(environ, start_response) except Exception: log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) - def __make_app(self, repo_name, repo_path): + def __make_app(self, repo_name, repo_path, extras): """ Make an wsgi application using dulserver :param repo_name: name of the repository :param repo_path: full path to the repository """ - _d = {'/' + repo_name: Repo(repo_path)} - backend = dulserver.DictBackend(_d) - gitserve = make_wsgi_chain(backend) - return gitserve + from rhodecode.lib.middleware.pygrack import make_wsgi_app + app = make_wsgi_app( + repo_root=safe_str(self.basepath), + repo_name=repo_name, + extras=extras, + ) + app = GunzipFilter(LimitedInputFilter(app)) + return app def __get_repository(self, environ): """ @@ -265,8 +297,12 @@ class SimpleGit(BaseVCSController): return op def _handle_githooks(self, repo_name, action, baseui, environ): - from rhodecode.lib.hooks import log_pull_action, log_push_action + """ + Handles pull action, push is handled by post-receive hook + """ + from rhodecode.lib.hooks import log_pull_action service = environ['QUERY_STRING'].split('=') + if len(service) < 2: return @@ -275,12 +311,11 @@ class SimpleGit(BaseVCSController): _repo = _repo.scm_instance _repo._repo.ui = baseui - push_hook = 'pretxnchangegroup.push_logger' - pull_hook = 'preoutgoing.pull_logger' _hooks = dict(baseui.configitems('hooks')) or {} - if action == 'push' and _hooks.get(push_hook): - log_push_action(ui=baseui, repo=_repo._repo) - elif action == 'pull' and _hooks.get(pull_hook): + if action == 'pull': + # stupid git, emulate pre-pull hook ! + pre_pull(ui=baseui, repo=_repo._repo) + if action == 'pull' and _hooks.get(RhodeCodeUi.HOOK_PULL): log_pull_action(ui=baseui, repo=_repo._repo) def __inject_extras(self, repo_path, baseui, extras={}): diff --git a/rhodecode/lib/middleware/simplehg.py b/rhodecode/lib/middleware/simplehg.py --- a/rhodecode/lib/middleware/simplehg.py +++ b/rhodecode/lib/middleware/simplehg.py @@ -33,14 +33,17 @@ from mercurial.error import RepoError from mercurial.hgweb import hgweb_mod from paste.httpheaders import REMOTE_USER, AUTH_TYPE +from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError, \ + HTTPBadRequest, HTTPNotAcceptable from rhodecode.lib.utils2 import safe_str from rhodecode.lib.base import BaseVCSController from rhodecode.lib.auth import get_container_username from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections +from rhodecode.lib.compat import json from rhodecode.model.db import User +from rhodecode.lib.exceptions import HTTPLockedRC -from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError log = logging.getLogger(__name__) @@ -68,9 +71,11 @@ class SimpleHg(BaseVCSController): def _handle_request(self, environ, start_response): if not is_mercurial(environ): return self.application(environ, start_response) + if not self._check_ssl(environ, start_response): + return HTTPNotAcceptable('SSL REQUIRED !')(environ, start_response) ipaddr = self._get_ip_addr(environ) - + username = None # skip passing error to error controller environ['pylons.status_code_redirect'] = True @@ -84,7 +89,7 @@ class SimpleHg(BaseVCSController): return HTTPInternalServerError()(environ, start_response) # quick check if that dir exists... - if is_valid_repo(repo_name, self.basepath) is False: + if is_valid_repo(repo_name, self.basepath, 'hg') is False: return HTTPNotFound()(environ, start_response) #====================================================================== @@ -131,21 +136,19 @@ class SimpleHg(BaseVCSController): #============================================================== # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME #============================================================== - if action in ['pull', 'push']: - try: - user = self.__get_user(username) - if user is None or not user.active: - return HTTPForbidden()(environ, start_response) - username = user.username - except: - log.error(traceback.format_exc()) - return HTTPInternalServerError()(environ, - start_response) + try: + user = self.__get_user(username) + if user is None or not user.active: + return HTTPForbidden()(environ, start_response) + username = user.username + except: + log.error(traceback.format_exc()) + return HTTPInternalServerError()(environ, start_response) - #check permissions for this repository - perm = self._check_permission(action, user, repo_name) - if perm is not True: - return HTTPForbidden()(environ, start_response) + #check permissions for this repository + perm = self._check_permission(action, user, repo_name) + if perm is not True: + return HTTPForbidden()(environ, start_response) # extras are injected into mercurial UI object and later available # in hg hooks executed by rhodecode @@ -155,14 +158,31 @@ class SimpleHg(BaseVCSController): 'action': action, 'repository': repo_name, 'scm': 'hg', + 'make_lock': None, + 'locked_by': [None, None] } - #====================================================================== # MERCURIAL REQUEST HANDLING #====================================================================== repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name)) log.debug('Repository path is %s' % repo_path) + # CHECK LOCKING only if it's not ANONYMOUS USER + if username != User.DEFAULT_USER: + log.debug('Checking locking on repository') + (make_lock, + locked, + locked_by) = self._check_locking_state( + environ=environ, action=action, + repo=repo_name, user_id=user.user_id + ) + # store the make_lock for later evaluation in hooks + extras.update({'make_lock': make_lock, + 'locked_by': locked_by}) + + # set the environ variables for this request + os.environ['RC_SCM_DATA'] = json.dumps(extras) + log.debug('HOOKS extras is %s' % extras) baseui = make_ui('db') self.__inject_extras(repo_path, baseui, extras) @@ -176,6 +196,9 @@ class SimpleHg(BaseVCSController): 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!') + return e(environ, start_response) except Exception: log.error(traceback.format_exc()) return HTTPInternalServerError()(environ, start_response) @@ -225,8 +248,11 @@ class SimpleHg(BaseVCSController): cmd = qry.split('=')[-1] if cmd in mapping: return mapping[cmd] - else: - return 'pull' + + return 'pull' + + raise Exception('Unable to detect pull/push action !!' + 'Are you using non standard command or client ?') def __inject_extras(self, repo_path, baseui, extras={}): """ diff --git a/rhodecode/lib/pidlock.py b/rhodecode/lib/pidlock.py --- a/rhodecode/lib/pidlock.py +++ b/rhodecode/lib/pidlock.py @@ -8,6 +8,7 @@ from multiprocessing.util import Finaliz from rhodecode.lib.compat import kill + class LockHeld(Exception): pass @@ -123,6 +124,10 @@ class DaemonLock(object): """ if self.debug: print 'creating a file %s and pid: %s' % (pidfile, lockname) + + dir_, file_ = os.path.split(pidfile) + if not os.path.isdir(dir_): + os.makedirs(dir_) pidfile = open(self.pidfile, "wb") pidfile.write(lockname) pidfile.close diff --git a/rhodecode/lib/profiler.py b/rhodecode/lib/profiler.py --- a/rhodecode/lib/profiler.py +++ b/rhodecode/lib/profiler.py @@ -1,5 +1,7 @@ from __future__ import with_statement +import gc +import objgraph import cProfile import pstats import cgi @@ -26,7 +28,7 @@ class ProfilingMiddleware(object): profiler.snapshot_stats() stats = pstats.Stats(profiler) - stats.sort_stats('cumulative') + stats.sort_stats('calls') #cummulative # Redirect output out = StringIO() @@ -44,6 +46,11 @@ class ProfilingMiddleware(object): 'border-top: 4px dashed red; padding: 1em;">') resp += cgi.escape(out.getvalue(), True) + ct = objgraph.show_most_common_types() + print ct + + resp += ct if ct else '---' + output = StringIO() pprint.pprint(environ, output, depth=3) diff --git a/rhodecode/lib/rcmail/message.py b/rhodecode/lib/rcmail/message.py --- a/rhodecode/lib/rcmail/message.py +++ b/rhodecode/lib/rcmail/message.py @@ -3,6 +3,7 @@ from rhodecode.lib.rcmail.response impor from rhodecode.lib.rcmail.exceptions import BadHeaders from rhodecode.lib.rcmail.exceptions import InvalidMessage + class Attachment(object): """ Encapsulates file attachment information. @@ -134,13 +135,13 @@ class Message(object): """ if not self.recipients: - raise InvalidMessage, "No recipients have been added" + raise InvalidMessage("No recipients have been added") if not self.body and not self.html: - raise InvalidMessage, "No body has been set" + raise InvalidMessage("No body has been set") if not self.sender: - raise InvalidMessage, "No sender address has been set" + raise InvalidMessage("No sender address has been set") if self.is_bad_headers(): raise BadHeaders diff --git a/rhodecode/lib/rcmail/response.py b/rhodecode/lib/rcmail/response.py --- a/rhodecode/lib/rcmail/response.py +++ b/rhodecode/lib/rcmail/response.py @@ -364,6 +364,7 @@ def to_message(mail, separator="; "): return out + class MIMEPart(MIMEBase): """ A reimplementation of nearly everything in email.mime to be more useful @@ -387,7 +388,8 @@ class MIMEPart(MIMEBase): self.set_payload(encoded, charset=charset) def extract_payload(self, mail): - if mail.body == None: return # only None, '' is still ok + if mail.body == None: + return # only None, '' is still ok ctype, ctype_params = mail.content_encoding['Content-Type'] cdisp, cdisp_params = mail.content_encoding['Content-Disposition'] @@ -415,7 +417,8 @@ class MIMEPart(MIMEBase): def header_to_mime_encoding(value, not_email=False, separator=", "): - if not value: return "" + if not value: + return "" encoder = Charset(DEFAULT_ENCODING) if type(value) == list: @@ -424,6 +427,7 @@ def header_to_mime_encoding(value, not_e else: return properly_encode_header(value, encoder, not_email) + def properly_encode_header(value, encoder, not_email): """ The only thing special (weird) about this function is that it tries diff --git a/rhodecode/lib/rcmail/smtp_mailer.py b/rhodecode/lib/rcmail/smtp_mailer.py --- a/rhodecode/lib/rcmail/smtp_mailer.py +++ b/rhodecode/lib/rcmail/smtp_mailer.py @@ -21,11 +21,13 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - +import time import logging import smtplib from socket import sslerror +from email.utils import formatdate from rhodecode.lib.rcmail.message import Message +from rhodecode.lib.rcmail.utils import DNS_NAME class SmtpMailer(object): @@ -59,14 +61,19 @@ class SmtpMailer(object): if isinstance(recipients, basestring): recipients = [recipients] + headers = { + 'Date': formatdate(time.time()) + } msg = Message(subject, recipients, body, html, self.mail_from, - recipients_separator=", ") + recipients_separator=", ", extra_headers=headers) raw_msg = msg.to_message() if self.ssl: - smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port) + smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port, + local_hostname=DNS_NAME.get_fqdn()) else: - smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port) + smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port, + local_hostname=DNS_NAME.get_fqdn()) if self.tls: smtp_serv.ehlo() @@ -91,4 +98,4 @@ class SmtpMailer(object): smtp_serv.quit() except sslerror: # sslerror is raised in tls connections on closing sometimes - pass + smtp_serv.close() diff --git a/rhodecode/lib/rcmail/utils.py b/rhodecode/lib/rcmail/utils.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/rcmail/utils.py @@ -0,0 +1,19 @@ +""" +Email message and email sending related helper functions. +""" + +import socket + + +# Cache the hostname, but do it lazily: socket.getfqdn() can take a couple of +# seconds, which slows down the restart of the server. +class CachedDnsName(object): + def __str__(self): + return self.get_fqdn() + + def get_fqdn(self): + if not hasattr(self, '_fqdn'): + self._fqdn = socket.getfqdn() + return self._fqdn + +DNS_NAME = CachedDnsName() diff --git a/rhodecode/lib/subprocessio.py b/rhodecode/lib/subprocessio.py new file mode 100644 --- /dev/null +++ b/rhodecode/lib/subprocessio.py @@ -0,0 +1,409 @@ +''' +Module provides a class allowing to wrap communication over subprocess.Popen +input, output, error streams into a meaningfull, non-blocking, concurrent +stream processor exposing the output data as an iterator fitting to be a +return value passed by a WSGI applicaiton to a WSGI server per PEP 3333. + +Copyright (c) 2011 Daniel Dotsenko + +This file is part of git_http_backend.py Project. + +git_http_backend.py Project is free software: you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public License as +published by the Free Software Foundation, either version 2.1 of the License, +or (at your option) any later version. + +git_http_backend.py Project 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with git_http_backend.py Project. +If not, see . +''' +import os +import subprocess +import threading +from rhodecode.lib.compat import deque, Event + + +class StreamFeeder(threading.Thread): + """ + Normal writing into pipe-like is blocking once the buffer is filled. + This thread allows a thread to seep data from a file-like into a pipe + without blocking the main thread. + We close inpipe once the end of the source stream is reached. + """ + def __init__(self, source): + super(StreamFeeder, self).__init__() + self.daemon = True + filelike = False + self.bytes = bytes() + if type(source) in (type(''), bytes, bytearray): # string-like + self.bytes = bytes(source) + else: # can be either file pointer or file-like + if type(source) in (int, long): # file pointer it is + ## converting file descriptor (int) stdin into file-like + try: + source = os.fdopen(source, 'rb', 16384) + except Exception: + pass + # let's see if source is file-like by now + try: + filelike = source.read + except Exception: + pass + if not filelike and not self.bytes: + raise TypeError("StreamFeeder's source object must be a readable " + "file-like, a file descriptor, or a string-like.") + self.source = source + self.readiface, self.writeiface = os.pipe() + + def run(self): + t = self.writeiface + if self.bytes: + os.write(t, self.bytes) + else: + s = self.source + b = s.read(4096) + while b: + os.write(t, b) + b = s.read(4096) + os.close(t) + + @property + def output(self): + return self.readiface + + +class InputStreamChunker(threading.Thread): + def __init__(self, source, target, buffer_size, chunk_size): + + super(InputStreamChunker, self).__init__() + + self.daemon = True # die die die. + + self.source = source + self.target = target + self.chunk_count_max = int(buffer_size / chunk_size) + 1 + self.chunk_size = chunk_size + + self.data_added = Event() + self.data_added.clear() + + self.keep_reading = Event() + self.keep_reading.set() + + self.EOF = Event() + self.EOF.clear() + + self.go = Event() + self.go.set() + + def stop(self): + self.go.clear() + self.EOF.set() + try: + # this is not proper, but is done to force the reader thread let + # go of the input because, if successful, .close() will send EOF + # down the pipe. + self.source.close() + except: + pass + + def run(self): + s = self.source + t = self.target + cs = self.chunk_size + ccm = self.chunk_count_max + kr = self.keep_reading + da = self.data_added + go = self.go + b = s.read(cs) + while b and go.is_set(): + if len(t) > ccm: + kr.clear() + kr.wait(2) +# # this only works on 2.7.x and up +# if not kr.wait(10): +# raise Exception("Timed out while waiting for input to be read.") + # instead we'll use this + if len(t) > ccm + 3: + raise IOError("Timed out while waiting for input from subprocess.") + t.append(b) + da.set() + b = s.read(cs) + self.EOF.set() + da.set() # for cases when done but there was no input. + + +class BufferedGenerator(): + ''' + Class behaves as a non-blocking, buffered pipe reader. + Reads chunks of data (through a thread) + from a blocking pipe, and attaches these to an array (Deque) of chunks. + Reading is halted in the thread when max chunks is internally buffered. + The .next() may operate in blocking or non-blocking fashion by yielding + '' if no data is ready + to be sent or by not returning until there is some data to send + When we get EOF from underlying source pipe we raise the marker to raise + StopIteration after the last chunk of data is yielded. + ''' + + def __init__(self, source, buffer_size=65536, chunk_size=4096, + starting_values=[], bottomless=False): + + if bottomless: + maxlen = int(buffer_size / chunk_size) + else: + maxlen = None + + self.data = deque(starting_values, maxlen) + + self.worker = InputStreamChunker(source, self.data, buffer_size, + chunk_size) + if starting_values: + self.worker.data_added.set() + self.worker.start() + + #################### + # Generator's methods + #################### + + def __iter__(self): + return self + + def next(self): + while not len(self.data) and not self.worker.EOF.is_set(): + self.worker.data_added.clear() + self.worker.data_added.wait(0.2) + if len(self.data): + self.worker.keep_reading.set() + return bytes(self.data.popleft()) + elif self.worker.EOF.is_set(): + raise StopIteration + + def throw(self, type, value=None, traceback=None): + if not self.worker.EOF.is_set(): + raise type(value) + + def start(self): + self.worker.start() + + def stop(self): + self.worker.stop() + + def close(self): + try: + self.worker.stop() + self.throw(GeneratorExit) + except (GeneratorExit, StopIteration): + pass + + def __del__(self): + self.close() + + #################### + # Threaded reader's infrastructure. + #################### + @property + def input(self): + return self.worker.w + + @property + def data_added_event(self): + return self.worker.data_added + + @property + def data_added(self): + return self.worker.data_added.is_set() + + @property + def reading_paused(self): + return not self.worker.keep_reading.is_set() + + @property + def done_reading_event(self): + ''' + Done_reding does not mean that the iterator's buffer is empty. + Iterator might have done reading from underlying source, but the read + chunks might still be available for serving through .next() method. + + @return An Event class instance. + ''' + return self.worker.EOF + + @property + def done_reading(self): + ''' + Done_reding does not mean that the iterator's buffer is empty. + Iterator might have done reading from underlying source, but the read + chunks might still be available for serving through .next() method. + + @return An Bool value. + ''' + return self.worker.EOF.is_set() + + @property + def length(self): + ''' + returns int. + + This is the lenght of the que of chunks, not the length of + the combined contents in those chunks. + + __len__() cannot be meaningfully implemented because this + reader is just flying throuh a bottomless pit content and + can only know the lenght of what it already saw. + + If __len__() on WSGI server per PEP 3333 returns a value, + the responce's length will be set to that. In order not to + confuse WSGI PEP3333 servers, we will not implement __len__ + at all. + ''' + return len(self.data) + + def prepend(self, x): + self.data.appendleft(x) + + def append(self, x): + self.data.append(x) + + def extend(self, o): + self.data.extend(o) + + def __getitem__(self, i): + return self.data[i] + + +class SubprocessIOChunker(object): + ''' + Processor class wrapping handling of subprocess IO. + + In a way, this is a "communicate()" replacement with a twist. + + - We are multithreaded. Writing in and reading out, err are all sep threads. + - We support concurrent (in and out) stream processing. + - The output is not a stream. It's a queue of read string (bytes, not unicode) + chunks. The object behaves as an iterable. You can "for chunk in obj:" us. + - We are non-blocking in more respects than communicate() + (reading from subprocess out pauses when internal buffer is full, but + does not block the parent calling code. On the flip side, reading from + slow-yielding subprocess may block the iteration until data shows up. This + does not block the parallel inpipe reading occurring parallel thread.) + + The purpose of the object is to allow us to wrap subprocess interactions into + and interable that can be passed to a WSGI server as the application's return + value. Because of stream-processing-ability, WSGI does not have to read ALL + of the subprocess's output and buffer it, before handing it to WSGI server for + HTTP response. Instead, the class initializer reads just a bit of the stream + to figure out if error ocurred or likely to occur and if not, just hands the + further iteration over subprocess output to the server for completion of HTTP + response. + + The real or perceived subprocess error is trapped and raised as one of + EnvironmentError family of exceptions + + Example usage: + # try: + # answer = SubprocessIOChunker( + # cmd, + # input, + # buffer_size = 65536, + # chunk_size = 4096 + # ) + # except (EnvironmentError) as e: + # print str(e) + # raise e + # + # return answer + + + ''' + def __init__(self, cmd, inputstream=None, buffer_size=65536, + chunk_size=4096, starting_values=[], **kwargs): + ''' + Initializes SubprocessIOChunker + + :param cmd: A Subprocess.Popen style "cmd". Can be string or array of strings + :param inputstream: (Default: None) A file-like, string, or file pointer. + :param buffer_size: (Default: 65536) A size of total buffer per stream in bytes. + :param chunk_size: (Default: 4096) A max size of a chunk. Actual chunk may be smaller. + :param starting_values: (Default: []) An array of strings to put in front of output que. + ''' + + if inputstream: + input_streamer = StreamFeeder(inputstream) + input_streamer.start() + inputstream = input_streamer.output + + if isinstance(cmd, (list, tuple)): + cmd = ' '.join(cmd) + + _shell = kwargs.get('shell') or True + kwargs['shell'] = _shell + _p = subprocess.Popen(cmd, + bufsize=-1, + stdin=inputstream, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs + ) + + bg_out = BufferedGenerator(_p.stdout, buffer_size, chunk_size, starting_values) + bg_err = BufferedGenerator(_p.stderr, 16000, 1, bottomless=True) + + while not bg_out.done_reading and not bg_out.reading_paused and not bg_err.length: + # doing this until we reach either end of file, or end of buffer. + bg_out.data_added_event.wait(1) + bg_out.data_added_event.clear() + + # at this point it's still ambiguous if we are done reading or just full buffer. + # Either way, if error (returned by ended process, or implied based on + # presence of stuff in stderr output) we error out. + # Else, we are happy. + _returncode = _p.poll() + if _returncode or (_returncode == None and bg_err.length): + try: + _p.terminate() + except: + pass + bg_out.stop() + bg_err.stop() + err = '%s' % ''.join(bg_err) + raise EnvironmentError("Subprocess exited due to an error:\n" + err) + + self.process = _p + self.output = bg_out + self.error = bg_err + + def __iter__(self): + return self + + def next(self): + if self.process.poll(): + err = '%s' % ''.join(self.error) + raise EnvironmentError("Subprocess exited due to an error:\n" + err) + return self.output.next() + + def throw(self, type, value=None, traceback=None): + if self.output.length or not self.output.done_reading: + raise type(value) + + def close(self): + try: + self.process.terminate() + except: + pass + try: + self.output.close() + except: + pass + try: + self.error.close() + except: + pass + + def __del__(self): + self.close() diff --git a/rhodecode/lib/utils.py b/rhodecode/lib/utils.py --- a/rhodecode/lib/utils.py +++ b/rhodecode/lib/utils.py @@ -51,8 +51,7 @@ from rhodecode.lib.caching_query import from rhodecode.model import meta from rhodecode.model.db import Repository, User, RhodeCodeUi, \ - UserLog, RepoGroup, RhodeCodeSetting, UserRepoGroupToPerm,\ - CacheInvalidation + UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation from rhodecode.model.meta import Session from rhodecode.model.repos_group import ReposGroupModel from rhodecode.lib.utils2 import safe_str, safe_unicode @@ -129,7 +128,7 @@ def action_logger(user, action, repo, ip """ if not sa: - sa = meta.Session + sa = meta.Session() try: if hasattr(user, 'user_id'): @@ -146,13 +145,14 @@ def action_logger(user, action, repo, ip repo_name = repo.lstrip('/') repo_obj = Repository.get_by_repo_name(repo_name) else: - raise Exception('You have to provide repository to action logger') + repo_obj = None + repo_name = '' user_log = UserLog() user_log.user_id = user_obj.user_id user_log.action = safe_unicode(action) - user_log.repository_id = repo_obj.repo_id + user_log.repository = repo_obj user_log.repository_name = repo_name user_log.action_date = datetime.datetime.now() @@ -203,19 +203,24 @@ def get_repos(path, recursive=False): return _get_repos(path) -def is_valid_repo(repo_name, base_path): +def is_valid_repo(repo_name, base_path, scm=None): """ - Returns True if given path is a valid repository False otherwise + Returns True if given path is a valid repository False otherwise. + If scm param is given also compare if given scm is the same as expected + from scm parameter :param repo_name: :param base_path: + :param scm: :return True: if given path is a valid repository """ full_path = os.path.join(safe_str(base_path), safe_str(repo_name)) try: - get_scm(full_path) + scm_ = get_scm(full_path) + if scm: + return scm_[0] == scm return True except VCSError: return False @@ -234,6 +239,15 @@ def is_valid_repos_group(repos_group_nam if is_valid_repo(repos_group_name, base_path): return False + try: + # we need to check bare git repos at higher level + # since we might match branches/hooks/info/objects or possible + # other things inside bare git repo + get_scm(os.path.dirname(full_path)) + return False + except VCSError: + pass + # check if it's a valid path if os.path.isdir(full_path): return True @@ -266,7 +280,7 @@ ui_sections = ['alias', 'auth', 'ui', 'web', ] -def make_ui(read_from='file', path=None, checkpaths=True): +def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True): """ A function that will read python rc files or database and make an mercurial ui object from read options @@ -296,7 +310,7 @@ def make_ui(read_from='file', path=None, baseui.setconfig(section, k, v) elif read_from == 'db': - sa = meta.Session + sa = meta.Session() ret = sa.query(RhodeCodeUi)\ .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\ .all() @@ -307,8 +321,12 @@ def make_ui(read_from='file', path=None, 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) - - meta.Session.remove() + if ui_.ui_key == 'push_ssl': + # force set push_ssl requirement to False, rhodecode + # handles that + baseui.setconfig(ui_.ui_section, ui_.ui_key, False) + if clear_session: + meta.Session.remove() return baseui @@ -337,50 +355,6 @@ def invalidate_cache(cache_key, *args): ScmModel().mark_for_invalidation(name) -class EmptyChangeset(BaseChangeset): - """ - An dummy empty changeset. It's possible to pass hash when creating - an EmptyChangeset - """ - - def __init__(self, cs='0' * 40, repo=None, requested_revision=None, - alias=None): - self._empty_cs = cs - self.revision = -1 - self.message = '' - self.author = '' - self.date = '' - self.repository = repo - self.requested_revision = requested_revision - self.alias = alias - - @LazyProperty - def raw_id(self): - """ - Returns raw string identifying this changeset, useful for web - representation. - """ - - return self._empty_cs - - @LazyProperty - def branch(self): - return get_backend(self.alias).DEFAULT_BRANCH_NAME - - @LazyProperty - def short_id(self): - return self.raw_id[:12] - - def get_file_changeset(self, path): - return self - - def get_file_content(self, path): - return u'' - - def get_file_size(self, path): - return 0 - - def map_groups(path): """ Given a full path to a repository, create all nested groups that this @@ -389,7 +363,7 @@ def map_groups(path): :param paths: full path to repository """ - sa = meta.Session + sa = meta.Session() groups = path.split(Repository.url_sep()) parent = None group = None @@ -418,7 +392,8 @@ def map_groups(path): return group -def repo2db_mapper(initial_repo_list, remove_obsolete=False): +def repo2db_mapper(initial_repo_list, remove_obsolete=False, + install_git_hook=False): """ maps all repos given in initial_repo_list, non existing repositories are created, if remove_obsolete is True it also check for db entries @@ -426,9 +401,12 @@ def repo2db_mapper(initial_repo_list, re :param initial_repo_list: list of repositories found by scanning methods :param remove_obsolete: check for obsolete entries in database + :param install_git_hook: if this is True, also check and install githook + for a repo if missing """ from rhodecode.model.repo import RepoModel - sa = meta.Session + from rhodecode.model.scm import ScmModel + sa = meta.Session() rm = RepoModel() user = sa.query(User).filter(User.admin == True).first() if user is None: @@ -437,30 +415,45 @@ def repo2db_mapper(initial_repo_list, re for name, repo in initial_repo_list.items(): group = map_groups(name) - if not rm.get_by_repo_name(name, cache=False): - log.info('repository %s not found creating default' % name) + db_repo = rm.get_by_repo_name(name) + # found repo that is on filesystem not in RhodeCode database + if not db_repo: + log.info('repository %s not found creating now' % name) added.append(name) - form_data = { - 'repo_name': name, - 'repo_name_full': name, - 'repo_type': repo.alias, - 'description': repo.description \ - if repo.description != 'unknown' else '%s repository' % name, - 'private': False, - 'group_id': getattr(group, 'group_id', None) - } - rm.create(form_data, user, just_db=True) + 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 + ) + # 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) + elif install_git_hook: + if db_repo.repo_type == 'git': + ScmModel().install_git_hook(db_repo.scm_instance) sa.commit() removed = [] if remove_obsolete: # remove from database those repositories that are not in the filesystem for repo in sa.query(Repository).all(): if repo.repo_name not in initial_repo_list.keys(): - log.debug("Removing non existing repository found in db %s" % + log.debug("Removing non existing repository found in db `%s`" % repo.repo_name) - removed.append(repo.repo_name) - sa.delete(repo) - sa.commit() + try: + sa.delete(repo) + sa.commit() + removed.append(repo.repo_name) + except: + #don't hold further removals on error + log.error(traceback.format_exc()) + sa.rollback() # clear cache keys log.debug("Clearing cache keys now...") @@ -557,7 +550,7 @@ def create_test_env(repos_test_path, con install test repository into tmp dir """ from rhodecode.lib.db_manage import DbManage - from rhodecode.tests import HG_REPO, TESTS_TMP_PATH + from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH # PART ONE create db dbconf = config['sqlalchemy.db1.url'] @@ -576,7 +569,7 @@ def create_test_env(repos_test_path, con dbmanage.admin_prompt() dbmanage.create_permissions() dbmanage.populate_default_permissions() - Session.commit() + Session().commit() # PART TWO make test repo log.debug('making test vcs repositories') @@ -592,12 +585,21 @@ def create_test_env(repos_test_path, con log.debug('remove %s' % data_path) shutil.rmtree(data_path) - #CREATE DEFAULT HG REPOSITORY + #CREATE DEFAULT TEST REPOS cur_dir = dn(dn(abspath(__file__))) tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz")) tar.extractall(jn(TESTS_TMP_PATH, HG_REPO)) tar.close() + cur_dir = dn(dn(abspath(__file__))) + tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz")) + tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO)) + tar.close() + + #LOAD VCS test stuff + from rhodecode.tests.vcs import setup_package + setup_package() + #============================================================================== # PASTER COMMANDS diff --git a/rhodecode/lib/utils2.py b/rhodecode/lib/utils2.py --- a/rhodecode/lib/utils2.py +++ b/rhodecode/lib/utils2.py @@ -24,6 +24,9 @@ # along with this program. If not, see . import re +import time +import datetime +from pylons.i18n.translation import _, ungettext from rhodecode.lib.vcs.utils.lazy import LazyProperty @@ -270,7 +273,6 @@ def engine_from_config(configuration, pr context._query_start_time = time.time() log.info(color_sql(">>>>> STARTING QUERY >>>>>")) - def after_cursor_execute(conn, cursor, statement, parameters, context, executemany): total = time.time() - context._query_start_time @@ -284,40 +286,76 @@ def engine_from_config(configuration, pr return engine -def age(curdate): +def age(prevdate): """ turns a datetime into an age string. - :param curdate: datetime object + :param prevdate: datetime object :rtype: unicode :returns: unicode words describing age """ - from datetime import datetime - from webhelpers.date import time_ago_in_words + order = ['year', 'month', 'day', 'hour', 'minute', 'second'] + deltas = {} + + # Get date parts deltas + now = datetime.datetime.now() + for part in order: + deltas[part] = getattr(now, part) - getattr(prevdate, part) + + # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00, + # not 1 hour, -59 minutes and -59 seconds) + + for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours + part = order[num] + carry_part = order[num - 1] - _ = lambda s: s + if deltas[part] < 0: + deltas[part] += length + deltas[carry_part] -= 1 - if not curdate: - return '' + # Same thing for days except that the increment depends on the (variable) + # number of days in the month + month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + if deltas['day'] < 0: + if prevdate.month == 2 and (prevdate.year % 4 == 0 and + (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)): + deltas['day'] += 29 + else: + deltas['day'] += month_lengths[prevdate.month - 1] + + deltas['month'] -= 1 - agescales = [(_(u"year"), 3600 * 24 * 365), - (_(u"month"), 3600 * 24 * 30), - (_(u"day"), 3600 * 24), - (_(u"hour"), 3600), - (_(u"minute"), 60), - (_(u"second"), 1), ] + if deltas['month'] < 0: + deltas['month'] += 12 + deltas['year'] -= 1 + + # Format the result + fmt_funcs = { + 'year': lambda d: ungettext(u'%d year', '%d years', d) % d, + 'month': lambda d: ungettext(u'%d month', '%d months', d) % d, + 'day': lambda d: ungettext(u'%d day', '%d days', d) % d, + 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d, + 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d, + 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d, + } - age = datetime.now() - curdate - age_seconds = (age.days * agescales[2][1]) + age.seconds - pos = 1 - for scale in agescales: - if scale[1] <= age_seconds: - if pos == 6: - pos = 5 - return '%s %s' % (time_ago_in_words(curdate, - agescales[pos][0]), _('ago')) - pos += 1 + for i, part in enumerate(order): + value = deltas[part] + if value == 0: + continue + + if i < 5: + sub_part = order[i + 1] + sub_value = deltas[sub_part] + else: + sub_value = 0 + + if sub_value == 0: + return _(u'%s ago') % fmt_funcs[part](value) + + return _(u'%s and %s ago') % (fmt_funcs[part](value), + fmt_funcs[sub_part](sub_value)) return _(u'just now') @@ -379,6 +417,7 @@ def get_changeset_safe(repo, rev): """ from rhodecode.lib.vcs.backends.base import BaseRepository from rhodecode.lib.vcs.exceptions import RepositoryError + from rhodecode.lib.vcs.backends.base import EmptyChangeset if not isinstance(repo, BaseRepository): raise Exception('You must pass an Repository ' 'object as first argument got %s', type(repo)) @@ -386,11 +425,24 @@ def get_changeset_safe(repo, rev): try: cs = repo.get_changeset(rev) except RepositoryError: - from rhodecode.lib.utils import EmptyChangeset cs = EmptyChangeset(requested_revision=rev) return cs +def datetime_to_time(dt): + if dt: + return time.mktime(dt.timetuple()) + + +def time_to_datetime(tm): + if tm: + if isinstance(tm, basestring): + try: + tm = float(tm) + except ValueError: + return + return datetime.datetime.fromtimestamp(tm) + MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})' @@ -405,3 +457,10 @@ def extract_mentioned_users(s): usrs.add(username) return sorted(list(usrs), key=lambda k: k.lower()) + + +class AttributeDict(dict): + def __getattr__(self, attr): + return self.get(attr, None) + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ diff --git a/rhodecode/lib/vcs/__init__.py b/rhodecode/lib/vcs/__init__.py --- a/rhodecode/lib/vcs/__init__.py +++ b/rhodecode/lib/vcs/__init__.py @@ -10,7 +10,7 @@ :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak. """ -VERSION = (0, 2, 3, 'dev') +VERSION = (0, 3, 0, 'dev') __version__ = '.'.join((str(each) for each in VERSION[:4])) diff --git a/rhodecode/lib/vcs/backends/git/changeset.py b/rhodecode/lib/vcs/backends/git/changeset.py --- a/rhodecode/lib/vcs/backends/git/changeset.py +++ b/rhodecode/lib/vcs/backends/git/changeset.py @@ -9,7 +9,7 @@ from rhodecode.lib.vcs.exceptions import from rhodecode.lib.vcs.exceptions import VCSError from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError -from rhodecode.lib.vcs.backends.base import BaseChangeset +from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyChangeset from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \ RemovedFileNode, SubModuleNode from rhodecode.lib.vcs.utils import safe_unicode @@ -25,17 +25,24 @@ class GitChangeset(BaseChangeset): def __init__(self, repository, revision): self._stat_modes = {} self.repository = repository - self.raw_id = revision - self.revision = repository.revisions.index(revision) - self.short_id = self.raw_id[:12] - self.id = self.raw_id try: - commit = self.repository._repo.get_object(self.raw_id) + commit = self.repository._repo.get_object(revision) + if isinstance(commit, objects.Tag): + revision = commit.object[1] + commit = self.repository._repo.get_object(commit.object[1]) except KeyError: - raise RepositoryError("Cannot get object with id %s" % self.raw_id) + raise RepositoryError("Cannot get object with id %s" % revision) + self.raw_id = revision + self.id = self.raw_id + self.short_id = self.raw_id[:12] self._commit = commit + self._tree_id = commit.tree + self._commiter_property = 'committer' + self._date_property = 'commit_time' + self._date_tz_property = 'commit_timezone' + self.revision = repository.revisions.index(revision) self.message = safe_unicode(commit.message) #self.branch = None @@ -45,12 +52,16 @@ class GitChangeset(BaseChangeset): @LazyProperty def author(self): - return safe_unicode(self._commit.committer) + return safe_unicode(getattr(self._commit, self._commiter_property)) @LazyProperty def date(self): - return date_fromtimestamp(self._commit.commit_time, - self._commit.commit_timezone) + return date_fromtimestamp(getattr(self._commit, self._date_property), + getattr(self._commit, self._date_tz_property)) + + @LazyProperty + def _timestamp(self): + return getattr(self._commit, self._date_property) @LazyProperty def status(self): @@ -83,7 +94,7 @@ class GitChangeset(BaseChangeset): if not path in self._paths: path = path.strip('/') # set root tree - tree = self.repository._repo[self._commit.tree] + tree = self.repository._repo[self._tree_id] if path == '': self._paths[''] = tree.id return tree.id @@ -132,8 +143,7 @@ class GitChangeset(BaseChangeset): return self._paths[path] def _get_kind(self, path): - id = self._get_id_for_path(path) - obj = self.repository._repo[id] + obj = self.repository._repo[self._get_id_for_path(path)] if isinstance(obj, objects.Blob): return NodeKind.FILE elif isinstance(obj, objects.Tree): @@ -148,7 +158,7 @@ class GitChangeset(BaseChangeset): Returns list of parents changesets. """ return [self.repository.get_changeset(parent) - for parent in self._commit.parents] + for parent in self._commit.parents] def next(self, branch=None): @@ -194,6 +204,13 @@ class GitChangeset(BaseChangeset): return _prev(self, branch) + def diff(self, ignore_whitespace=True, context=3): + rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET + rev2 = self + return ''.join(self.repository.get_diff(rev1, rev2, + ignore_whitespace=ignore_whitespace, + context=context)) + def get_file_mode(self, path): """ Returns stat mode of the file at the given ``path``. @@ -254,10 +271,11 @@ class GitChangeset(BaseChangeset): # --root ==> doesn't put '^' character for bounderies # -r sha ==> blames for the given revision so, se = self.repository.run_git_command(cmd) + annotate = [] for i, blame_line in enumerate(so.split('\n')[:-1]): ln_no = i + 1 - id, line = re.split(r' \(.+?\) ', blame_line, 1) + id, line = re.split(r' ', blame_line, 1) annotate.append((ln_no, self.repository.get_changeset(id), line)) return annotate @@ -363,10 +381,10 @@ class GitChangeset(BaseChangeset): raise NodeDoesNotExistError("Cannot find one of parents' " "directories for a given path: %s" % path) - als = self.repository.alias _GL = lambda m: m and objects.S_ISGITLINK(m) if _GL(self._stat_modes.get(path)): - node = SubModuleNode(path, url=None, changeset=id_, alias=als) + node = SubModuleNode(path, url=None, changeset=id_, + alias=self.repository.alias) else: obj = self.repository._repo.get_object(id_) @@ -392,39 +410,56 @@ class GitChangeset(BaseChangeset): """ Get's a fast accessible file changes for given changeset """ - - return self.added + self.changed + a, m, d = self._changes_cache + return list(a.union(m).union(d)) @LazyProperty def _diff_name_status(self): output = [] for parent in self.parents: - cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id, self.raw_id) + cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id, + self.raw_id) so, se = self.repository.run_git_command(cmd) output.append(so.strip()) return '\n'.join(output) + @LazyProperty + def _changes_cache(self): + added = set() + modified = set() + deleted = set() + _r = self.repository._repo + + parents = self.parents + if not self.parents: + parents = [EmptyChangeset()] + for parent in parents: + if isinstance(parent, EmptyChangeset): + oid = None + else: + oid = _r[parent.raw_id].tree + changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree) + for (oldpath, newpath), (_, _), (_, _) in changes: + if newpath and oldpath: + modified.add(newpath) + elif newpath and not oldpath: + added.add(newpath) + elif not newpath and oldpath: + deleted.add(oldpath) + return added, modified, deleted + def _get_paths_for_status(self, status): """ Returns sorted list of paths for given ``status``. :param status: one of: *added*, *modified* or *deleted* """ - paths = set() - char = status[0].upper() - for line in self._diff_name_status.splitlines(): - if not line: - continue - - if line.startswith(char): - splitted = line.split(char, 1) - if not len(splitted) == 2: - raise VCSError("Couldn't parse diff result:\n%s\n\n and " - "particularly that line: %s" % (self._diff_name_status, - line)) - _path = splitted[1].strip() - paths.add(_path) - return sorted(paths) + a, m, d = self._changes_cache + return sorted({ + 'added': list(a), + 'modified': list(m), + 'deleted': list(d)}[status] + ) @LazyProperty def added(self): diff --git a/rhodecode/lib/vcs/backends/git/inmemory.py b/rhodecode/lib/vcs/backends/git/inmemory.py --- a/rhodecode/lib/vcs/backends/git/inmemory.py +++ b/rhodecode/lib/vcs/backends/git/inmemory.py @@ -83,7 +83,8 @@ class GitInMemoryChangeset(BaseInMemoryC curtree = newtree parent[reversed_dirnames[-1]] = DIRMOD, curtree.id else: - parent.add(node.mode, node_path, blob.id) + parent.add(name=node_path, mode=node.mode, hexsha=blob.id) + new_trees.append(parent) # Update ancestors for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in @@ -123,7 +124,7 @@ class GitInMemoryChangeset(BaseInMemoryC commit.parents = [p._commit.id for p in self.parents if p] commit.author = commit.committer = safe_str(author) commit.encoding = ENCODING - commit.message = safe_str(message) + ' ' + commit.message = safe_str(message) # Compute date if date is None: @@ -148,6 +149,8 @@ class GitInMemoryChangeset(BaseInMemoryC # Update vcs repository object & recreate dulwich repo self.repository.revisions.append(commit.id) self.repository._repo = Repo(self.repository.path) + # invalidate parsed refs after commit + self.repository._parsed_refs = self.repository._get_parsed_refs() tip = self.repository.get_changeset() self.reset() return tip diff --git a/rhodecode/lib/vcs/backends/git/repository.py b/rhodecode/lib/vcs/backends/git/repository.py --- a/rhodecode/lib/vcs/backends/git/repository.py +++ b/rhodecode/lib/vcs/backends/git/repository.py @@ -13,6 +13,10 @@ import os import re import time import posixpath +import logging +import traceback +import urllib +import urllib2 from dulwich.repo import Repo, NotGitRepository #from dulwich.config import ConfigFile from string import Template @@ -33,6 +37,10 @@ from .workdir import GitWorkdir from .changeset import GitChangeset from .inmemory import GitInMemoryChangeset from .config import ConfigFile +from rhodecode.lib import subprocessio + + +log = logging.getLogger(__name__) class GitRepository(BaseRepository): @@ -66,6 +74,7 @@ class GitRepository(BaseRepository): 'config'), abspath(get_user_home(), '.gitconfig'), ] + self.bare = self._repo.bare @LazyProperty def revisions(self): @@ -94,28 +103,32 @@ class GitRepository(BaseRepository): cmd = [cmd] _str_cmd = True - cmd = ['GIT_CONFIG_NOGLOBAL=1', 'git'] + _copts + cmd + gitenv = os.environ + # need to clean fix GIT_DIR ! + if 'GIT_DIR' in gitenv: + del gitenv['GIT_DIR'] + gitenv['GIT_CONFIG_NOGLOBAL'] = '1' + + cmd = ['git'] + _copts + cmd if _str_cmd: cmd = ' '.join(cmd) try: opts = dict( - shell=isinstance(cmd, basestring), - stdout=PIPE, - stderr=PIPE) + env=gitenv, + shell=False, + ) if os.path.isdir(self.path): opts['cwd'] = self.path - p = Popen(cmd, **opts) - except OSError, err: + p = subprocessio.SubprocessIOChunker(cmd, **opts) + except (EnvironmentError, OSError), err: + log.error(traceback.format_exc()) raise RepositoryError("Couldn't run git command (%s).\n" - "Original error was:%s" % (cmd, err)) - so, se = p.communicate() - if not se.startswith("fatal: bad default revision 'HEAD'") and \ - p.returncode != 0: - raise RepositoryError("Couldn't run git command (%s).\n" - "stderr:\n%s" % (cmd, se)) - return so, se + "Original error was:%s" % (cmd, err)) - def _check_url(self, url): + return ''.join(p.output), ''.join(p.error) + + @classmethod + def _check_url(cls, url): """ Functon will check given url and try to verify if it's a valid link. Sometimes it may happened that mercurial will issue basic @@ -124,9 +137,45 @@ class GitRepository(BaseRepository): On failures it'll raise urllib2.HTTPError """ + from mercurial.util import url as Url - #TODO: implement this - pass + # those authnadlers are patched for python 2.6.5 bug an + # infinit looping when given invalid resources + from mercurial.url import httpbasicauthhandler, httpdigestauthhandler + + # check first if it's not an local url + if os.path.isdir(url) or url.startswith('file:'): + return True + + if('+' in url[:url.find('://')]): + url = url[url.find('+') + 1:] + + handlers = [] + test_uri, authinfo = Url(url).authinfo() + if not test_uri.endswith('info/refs'): + test_uri = test_uri.rstrip('/') + '/info/refs' + if authinfo: + #create a password manager + passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm() + passmgr.add_password(*authinfo) + + handlers.extend((httpbasicauthhandler(passmgr), + httpdigestauthhandler(passmgr))) + + o = urllib2.build_opener(*handlers) + o.addheaders = [('User-Agent', 'git/1.7.8.0')] # fake some git + + q = {"service": 'git-upload-pack'} + qs = '?%s' % urllib.urlencode(q) + cu = "%s%s" % (test_uri, qs) + req = urllib2.Request(cu, None, {}) + + try: + resp = o.open(req) + return resp.code == 200 + except Exception, e: + # means it cannot be cloned + raise urllib2.URLError("[%s] %s" % (url, e)) def _get_repo(self, create, src_url=None, update_after_clone=False, bare=False): @@ -137,7 +186,7 @@ class GitRepository(BaseRepository): "given (clone operation creates repository)") try: if create and src_url: - self._check_url(src_url) + GitRepository._check_url(src_url) self.clone(src_url, update_after_clone, bare) return Repo(self.path) elif create: @@ -152,15 +201,26 @@ class GitRepository(BaseRepository): raise RepositoryError(err) def _get_all_revisions(self): - cmd = 'rev-list --all --date-order' + # we must check if this repo is not empty, since later command + # fails if it is. And it's cheaper to ask than throw the subprocess + # errors + try: + self._repo.head() + except KeyError: + return [] + cmd = 'rev-list --all --reverse --date-order' try: so, se = self.run_git_command(cmd) except RepositoryError: # Can be raised for empty repositories return [] - revisions = so.splitlines() - revisions.reverse() - return revisions + return so.splitlines() + + def _get_all_revisions2(self): + #alternate implementation using dulwich + includes = [x[1][0] for x in self._parsed_refs.iteritems() + if x[1][1] != 'T'] + return [c.commit.id for c in self._repo.get_walker(include=includes)] def _get_revision(self, revision): """ @@ -186,7 +246,17 @@ class GitRepository(BaseRepository): "for this repository %s" % (revision, self)) elif is_bstr(revision): - if not pattern.match(revision) or revision not in self.revisions: + # get by branch/tag name + _ref_revision = self._parsed_refs.get(revision) + _tags_shas = self.tags.values() + if _ref_revision: # and _ref_revision[1] in ['H', 'RH', 'T']: + return _ref_revision[0] + + # maybe it's a tag ? we don't have them in self.revisions + elif revision in _tags_shas: + return _tags_shas[_tags_shas.index(revision)] + + elif not pattern.match(revision) or revision not in self.revisions: raise ChangesetDoesNotExistError("Revision %r does not exist " "for this repository %s" % (revision, self)) @@ -226,9 +296,10 @@ class GitRepository(BaseRepository): try: return time.mktime(self.get_changeset().date.timetuple()) except RepositoryError: + idx_loc = '' if self.bare else '.git' # fallback to filesystem - in_path = os.path.join(self.path, '.git', "index") - he_path = os.path.join(self.path, '.git', "HEAD") + in_path = os.path.join(self.path, idx_loc, "index") + he_path = os.path.join(self.path, idx_loc, "HEAD") if os.path.exists(in_path): return os.stat(in_path).st_mtime else: @@ -236,8 +307,9 @@ class GitRepository(BaseRepository): @LazyProperty def description(self): + idx_loc = '' if self.bare else '.git' undefined_description = u'unknown' - description_path = os.path.join(self.path, '.git', 'description') + description_path = os.path.join(self.path, idx_loc, 'description') if os.path.isfile(description_path): return safe_unicode(open(description_path).read()) else: @@ -252,38 +324,24 @@ class GitRepository(BaseRepository): def branches(self): if not self.revisions: return {} - refs = self._repo.refs.as_dict() sortkey = lambda ctx: ctx[0] - _branches = [('/'.join(ref.split('/')[2:]), head) - for ref, head in refs.items() - if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')] + _branches = [(x[0], x[1][0]) + for x in self._parsed_refs.iteritems() if x[1][1] == 'H'] return OrderedDict(sorted(_branches, key=sortkey, reverse=False)) - def _heads(self, reverse=False): - refs = self._repo.get_refs() - heads = {} - - for key, val in refs.items(): - for ref_key in ['refs/heads/', 'refs/remotes/origin/']: - if key.startswith(ref_key): - n = key[len(ref_key):] - if n not in ['HEAD']: - heads[n] = val - - return heads if reverse else dict((y,x) for x,y in heads.iteritems()) + @LazyProperty + def tags(self): + return self._get_tags() def _get_tags(self): if not self.revisions: return {} + sortkey = lambda ctx: ctx[0] - _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in - self._repo.get_refs().items() if ref.startswith('refs/tags/')] + _tags = [(x[0], x[1][0]) + for x in self._parsed_refs.iteritems() if x[1][1] == 'T'] return OrderedDict(sorted(_tags, key=sortkey, reverse=True)) - @LazyProperty - def tags(self): - return self._get_tags() - def tag(self, name, user, revision=None, message=None, date=None, **kwargs): """ @@ -304,6 +362,7 @@ class GitRepository(BaseRepository): changeset.raw_id) self._repo.refs["refs/tags/%s" % name] = changeset._commit.id + self._parsed_refs = self._get_parsed_refs() self.tags = self._get_tags() return changeset @@ -323,10 +382,42 @@ class GitRepository(BaseRepository): tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name) try: os.remove(tagpath) + self._parsed_refs = self._get_parsed_refs() self.tags = self._get_tags() except OSError, e: raise RepositoryError(e.strerror) + @LazyProperty + def _parsed_refs(self): + return self._get_parsed_refs() + + def _get_parsed_refs(self): + refs = self._repo.get_refs() + keys = [('refs/heads/', 'H'), + ('refs/remotes/origin/', 'RH'), + ('refs/tags/', 'T')] + _refs = {} + for ref, sha in refs.iteritems(): + for k, type_ in keys: + if ref.startswith(k): + _key = ref[len(k):] + _refs[_key] = [sha, type_] + break + return _refs + + def _heads(self, reverse=False): + refs = self._repo.get_refs() + heads = {} + + for key, val in refs.items(): + for ref_key in ['refs/heads/', 'refs/remotes/origin/']: + if key.startswith(ref_key): + n = key[len(ref_key):] + if n not in ['HEAD']: + heads[n] = val + + return heads if reverse else dict((y, x) for x, y in heads.iteritems()) + def get_changeset(self, revision=None): """ Returns ``GitChangeset`` object representing commit from git repository @@ -429,6 +520,12 @@ class GitRepository(BaseRepository): if ignore_whitespace: flags.append('-w') + if hasattr(rev1, 'raw_id'): + rev1 = getattr(rev1, 'raw_id') + + if hasattr(rev2, 'raw_id'): + rev2 = getattr(rev2, 'raw_id') + if rev1 == self.EMPTY_CHANGESET: rev2 = self.get_changeset(rev2).raw_id cmd = ' '.join(['show'] + flags + [rev2]) @@ -492,6 +589,17 @@ class GitRepository(BaseRepository): # If error occurs run_git_command raises RepositoryError already self.run_git_command(cmd) + def fetch(self, url): + """ + 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 + self.run_git_command(cmd) + @LazyProperty def workdir(self): """ diff --git a/rhodecode/lib/vcs/backends/hg/changeset.py b/rhodecode/lib/vcs/backends/hg/changeset.py --- a/rhodecode/lib/vcs/backends/hg/changeset.py +++ b/rhodecode/lib/vcs/backends/hg/changeset.py @@ -12,8 +12,7 @@ from rhodecode.lib.vcs.nodes import Adde from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp from rhodecode.lib.vcs.utils.lazy import LazyProperty from rhodecode.lib.vcs.utils.paths import get_dirs_for_path - -from ...utils.hgcompat import archival, hex +from rhodecode.lib.vcs.utils.hgcompat import archival, hex class MercurialChangeset(BaseChangeset): @@ -53,6 +52,10 @@ class MercurialChangeset(BaseChangeset): return date_fromtimestamp(*self._ctx.date()) @LazyProperty + def _timestamp(self): + return self._ctx.date()[0] + + @LazyProperty def status(self): """ Returns modified, added, removed, deleted files for current changeset @@ -136,6 +139,11 @@ class MercurialChangeset(BaseChangeset): return _prev(self, branch) + def diff(self, ignore_whitespace=True, context=3): + return ''.join(self._ctx.diff(git=True, + ignore_whitespace=ignore_whitespace, + context=context)) + def _fix_path(self, path): """ Paths are stored without trailing slash so we need to get rid off it if diff --git a/rhodecode/lib/vcs/backends/hg/inmemory.py b/rhodecode/lib/vcs/backends/hg/inmemory.py --- a/rhodecode/lib/vcs/backends/hg/inmemory.py +++ b/rhodecode/lib/vcs/backends/hg/inmemory.py @@ -4,7 +4,7 @@ import errno from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset from rhodecode.lib.vcs.exceptions import RepositoryError -from ...utils.hgcompat import memfilectx, memctx, hex, tolocal +from rhodecode.lib.vcs.utils.hgcompat import memfilectx, memctx, hex, tolocal class MercurialInMemoryChangeset(BaseInMemoryChangeset): @@ -32,7 +32,8 @@ class MercurialInMemoryChangeset(BaseInM from .repository import MercurialRepository if not isinstance(message, unicode) or not isinstance(author, unicode): raise RepositoryError('Given message and author needs to be ' - 'an instance') + 'an instance got %r & %r instead' + % (type(message), type(author))) if branch is None: branch = MercurialRepository.DEFAULT_BRANCH_NAME diff --git a/rhodecode/lib/vcs/backends/hg/repository.py b/rhodecode/lib/vcs/backends/hg/repository.py --- a/rhodecode/lib/vcs/backends/hg/repository.py +++ b/rhodecode/lib/vcs/backends/hg/repository.py @@ -18,7 +18,7 @@ from rhodecode.lib.vcs.utils.lazy import from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict from rhodecode.lib.vcs.utils.paths import abspath -from ...utils.hgcompat import ui, nullid, match, patch, diffopts, clone, \ +from rhodecode.lib.vcs.utils.hgcompat import ui, nullid, match, patch, diffopts, clone, \ get_contact, pull, localrepository, RepoLookupError, Abort, RepoError, hex @@ -231,6 +231,12 @@ class MercurialRepository(BaseRepository :param context: How many lines before/after changed lines should be shown. Defaults to ``3``. """ + if hasattr(rev1, 'raw_id'): + rev1 = getattr(rev1, 'raw_id') + + if hasattr(rev2, 'raw_id'): + rev2 = getattr(rev2, 'raw_id') + # Check if given revisions are present at repository (may raise # ChangesetDoesNotExistError) if rev1 != self.EMPTY_CHANGESET: @@ -243,7 +249,8 @@ class MercurialRepository(BaseRepository ignorews=ignore_whitespace, context=context))) - def _check_url(self, url): + @classmethod + def _check_url(cls, url): """ Function will check given url and try to verify if it's a valid link. Sometimes it may happened that mercurial will issue basic @@ -264,6 +271,9 @@ class MercurialRepository(BaseRepository if os.path.isdir(url) or url.startswith('file:'): return True + if('+' in url[:url.find('://')]): + url = url[url.find('+') + 1:] + handlers = [] test_uri, authinfo = Url(url).authinfo() @@ -290,7 +300,7 @@ class MercurialRepository(BaseRepository return resp.code == 200 except Exception, e: # means it cannot be cloned - raise urllib2.URLError(e) + raise urllib2.URLError("[%s] %s" % (url, e)) def _get_repo(self, create, src_url=None, update_after_clone=False): """ @@ -302,6 +312,7 @@ class MercurialRepository(BaseRepository location at given clone_point. Additionally it'll make update to working copy accordingly to ``update_after_clone`` flag """ + try: if src_url: url = str(self._get_url(src_url)) @@ -309,12 +320,13 @@ class MercurialRepository(BaseRepository if not update_after_clone: opts.update({'noupdate': True}) try: - self._check_url(url) + MercurialRepository._check_url(url) clone(self.baseui, url, self.path, **opts) # except urllib2.URLError: # raise Abort("Got HTTP 404 error") except Exception: raise + # Don't try to create if we've already cloned repo create = False return localrepository(self.baseui, self.path, create=create) diff --git a/rhodecode/lib/vcs/backends/hg/workdir.py b/rhodecode/lib/vcs/backends/hg/workdir.py --- a/rhodecode/lib/vcs/backends/hg/workdir.py +++ b/rhodecode/lib/vcs/backends/hg/workdir.py @@ -1,7 +1,7 @@ from rhodecode.lib.vcs.backends.base import BaseWorkdir from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError -from ...utils.hgcompat import hg_merge +from rhodecode.lib.vcs.utils.hgcompat import hg_merge class MercurialWorkdir(BaseWorkdir): diff --git a/rhodecode/lib/vcs/nodes.py b/rhodecode/lib/vcs/nodes.py --- a/rhodecode/lib/vcs/nodes.py +++ b/rhodecode/lib/vcs/nodes.py @@ -16,7 +16,7 @@ import mimetypes from pygments import lexers from rhodecode.lib.vcs.utils.lazy import LazyProperty -from rhodecode.lib.vcs.utils import safe_unicode, safe_str +from rhodecode.lib.vcs.utils import safe_unicode from rhodecode.lib.vcs.exceptions import NodeError from rhodecode.lib.vcs.exceptions import RemovedFileNodeError from rhodecode.lib.vcs.backends.base import EmptyChangeset @@ -422,7 +422,7 @@ class FileNode(Node): def __repr__(self): return '<%s %r @ %s>' % (self.__class__.__name__, self.path, - self.changeset.short_id) + getattr(self.changeset, 'short_id', '')) class RemovedFileNode(FileNode): @@ -431,8 +431,10 @@ class RemovedFileNode(FileNode): name, kind or state (or methods/attributes checking those two) would raise RemovedFileNodeError. """ - ALLOWED_ATTRIBUTES = ['name', 'path', 'state', 'is_root', 'is_file', - 'is_dir', 'kind', 'added', 'changed', 'not_changed', 'removed'] + ALLOWED_ATTRIBUTES = [ + 'name', 'path', 'state', 'is_root', 'is_file', 'is_dir', 'kind', + 'added', 'changed', 'not_changed', 'removed' + ] def __init__(self, path): """ @@ -557,7 +559,7 @@ class DirNode(Node): def __repr__(self): return '<%s %r @ %s>' % (self.__class__.__name__, self.path, - self.changeset.short_id) + getattr(self.changeset, 'short_id', '')) class RootNode(DirNode): @@ -591,7 +593,7 @@ class SubModuleNode(Node): def __repr__(self): return '<%s %r @ %s>' % (self.__class__.__name__, self.path, - self.changeset.short_id) + getattr(self.changeset, 'short_id', '')) def _extract_submodule_url(self): if self.alias == 'git': diff --git a/rhodecode/lib/vcs/utils/hgcompat.py b/rhodecode/lib/vcs/utils/hgcompat.py --- a/rhodecode/lib/vcs/utils/hgcompat.py +++ b/rhodecode/lib/vcs/utils/hgcompat.py @@ -12,3 +12,6 @@ from mercurial.match import match from mercurial.mdiff import diffopts from mercurial.node import hex from mercurial.encoding import tolocal +from mercurial import discovery +from mercurial import localrepo +from mercurial import scmutil \ No newline at end of file diff --git a/rhodecode/lib/vcs/utils/lazy.py b/rhodecode/lib/vcs/utils/lazy.py --- a/rhodecode/lib/vcs/utils/lazy.py +++ b/rhodecode/lib/vcs/utils/lazy.py @@ -17,11 +17,12 @@ class LazyProperty(object): def __init__(self, func): self._func = func + self.__module__ = func.__module__ self.__name__ = func.__name__ self.__doc__ = func.__doc__ def __get__(self, obj, klass=None): if obj is None: - return None + return self result = obj.__dict__[self.__name__] = self._func(obj) return result diff --git a/rhodecode/lib/vcs/utils/paths.py b/rhodecode/lib/vcs/utils/paths.py --- a/rhodecode/lib/vcs/utils/paths.py +++ b/rhodecode/lib/vcs/utils/paths.py @@ -29,8 +29,9 @@ def get_dir_size(path): pass return size + def get_user_home(): """ Returns home path of the user. """ - return os.getenv('HOME', os.getenv('USERPROFILE')) + return os.getenv('HOME', os.getenv('USERPROFILE')) or '' diff --git a/rhodecode/model/__init__.py b/rhodecode/model/__init__.py --- a/rhodecode/model/__init__.py +++ b/rhodecode/model/__init__.py @@ -42,8 +42,8 @@ # along with this program. If not, see . import logging - from rhodecode.model import meta +from rhodecode.lib.utils2 import safe_str log = logging.getLogger(__name__) @@ -68,11 +68,13 @@ class BaseModel(object): :param sa: If passed it reuses this session instead of creating a new one """ + cls = None # override in child class + def __init__(self, sa=None): if sa is not None: self.sa = sa else: - self.sa = meta.Session + self.sa = meta.Session() def _get_instance(self, cls, instance, callback=None): """ @@ -85,7 +87,7 @@ class BaseModel(object): if isinstance(instance, cls): return instance - elif isinstance(instance, (int, long)) or str(instance).isdigit(): + elif isinstance(instance, (int, long)) or safe_str(instance).isdigit(): return cls.get(instance) else: if instance: @@ -96,3 +98,42 @@ class BaseModel(object): ) else: return callback(instance) + + def _get_user(self, user): + """ + Helper method to get user by ID, or username fallback + + :param user: + :type user: UserID, username, or User instance + """ + from rhodecode.model.db import User + return self._get_instance(User, user, + callback=User.get_by_username) + + def _get_repo(self, repository): + """ + Helper method to get repository by ID, or repository name + + :param repository: + :type repository: RepoID, repository name or Repository Instance + """ + from rhodecode.model.db import Repository + return self._get_instance(Repository, repository, + callback=Repository.get_by_repo_name) + + def _get_perm(self, permission): + """ + Helper method to get permission by ID, or permission name + + :param permission: + :type permission: PermissionID, permission_name or Permission instance + """ + from rhodecode.model.db import Permission + return self._get_instance(Permission, permission, + callback=Permission.get_by_key) + + def get_all(self): + """ + Returns all instances of what is defined in `cls` class variable + """ + return self.cls.getAll() diff --git a/rhodecode/model/changeset_status.py b/rhodecode/model/changeset_status.py new file mode 100644 --- /dev/null +++ b/rhodecode/model/changeset_status.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.changeset_status + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + + :created_on: Apr 30, 2012 + :author: marcink + :copyright: (C) 2011-2012 Marcin Kuzminski + :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 . + + +import logging +from collections import defaultdict + +from rhodecode.model import BaseModel +from rhodecode.model.db import ChangesetStatus, PullRequest +from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError + +log = logging.getLogger(__name__) + + +class ChangesetStatusModel(BaseModel): + + cls = ChangesetStatus + + def __get_changeset_status(self, changeset_status): + return self._get_instance(ChangesetStatus, changeset_status) + + def __get_pull_request(self, pull_request): + return self._get_instance(PullRequest, pull_request) + + def _get_status_query(self, repo, revision, pull_request, + with_revisions=False): + repo = self._get_repo(repo) + + q = ChangesetStatus.query()\ + .filter(ChangesetStatus.repo == repo) + if not with_revisions: + q = q.filter(ChangesetStatus.version == 0) + + if revision: + q = q.filter(ChangesetStatus.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetStatus.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request') + q.order_by(ChangesetStatus.version.asc()) + return q + + def calculate_status(self, statuses_by_reviewers): + """ + leading one wins, if number of occurences are equal than weaker wins + + :param statuses_by_reviewers: + """ + status = None + votes = defaultdict(int) + reviewers_number = len(statuses_by_reviewers) + for user, statuses in statuses_by_reviewers: + if statuses: + ver, latest = statuses[0] + votes[latest.status] += 1 + else: + votes[ChangesetStatus.DEFAULT] += 1 + + if votes.get(ChangesetStatus.STATUS_APPROVED) == reviewers_number: + return ChangesetStatus.STATUS_APPROVED + else: + return ChangesetStatus.STATUS_UNDER_REVIEW + + def get_statuses(self, repo, revision=None, pull_request=None, + with_revisions=False): + q = self._get_status_query(repo, revision, pull_request, + with_revisions) + return q.all() + + def get_status(self, repo, revision=None, pull_request=None): + """ + 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: + """ + 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) + + def set_status(self, repo, status, user, comment, 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 + """ + repo = self._get_repo(repo) + + q = ChangesetStatus.query() + + 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) + cur_statuses = q.all() + + #if statuses exists and last is associated with a closed pull request + # we need to check if we can allow this status change + if (dont_allow_on_closed_pull_request and cur_statuses + and getattr(cur_statuses[0].pull_request, 'status', '') + == PullRequest.STATUS_CLOSED): + raise StatusChangeOnClosedPullRequestError( + 'Changing status on closed pull request is not allowed' + ) + + if cur_statuses: + for st in cur_statuses: + st.version += 1 + self.sa.add(st) + + def _create_status(user, repo, status, comment, revision, pull_request): + new_status = ChangesetStatus() + new_status.author = self._get_user(user) + new_status.repo = self._get_repo(repo) + new_status.status = status + new_status.comment = comment + new_status.revision = revision + new_status.pull_request = pull_request + return new_status + + if revision: + new_status = _create_status(user=user, repo=repo, status=status, + comment=comment, revision=revision, + pull_request=None) + self.sa.add(new_status) + return new_status + elif pull_request: + #pull request can have more than one revision associated to it + #we need to create new version for each one + new_statuses = [] + repo = pull_request.org_repo + for rev in pull_request.revisions: + new_status = _create_status(user=user, repo=repo, + status=status, comment=comment, + revision=rev, + pull_request=pull_request) + new_statuses.append(new_status) + self.sa.add(new_status) + return new_statuses diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -32,7 +32,8 @@ from sqlalchemy.util.compat import defau from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode from rhodecode.lib import helpers as h from rhodecode.model import BaseModel -from rhodecode.model.db import ChangesetComment, User, Repository, Notification +from rhodecode.model.db import ChangesetComment, User, Repository, \ + Notification, PullRequest from rhodecode.model.notification import NotificationModel log = logging.getLogger(__name__) @@ -40,9 +41,14 @@ log = logging.getLogger(__name__) class ChangesetCommentsModel(BaseModel): + cls = ChangesetComment + def __get_changeset_comment(self, changeset_comment): return self._get_instance(ChangesetComment, changeset_comment) + def __get_pull_request(self, pull_request): + return self._get_instance(PullRequest, pull_request) + def _extract_mentions(self, s): user_objects = [] for username in extract_mentioned_users(s): @@ -51,41 +57,60 @@ class ChangesetCommentsModel(BaseModel): user_objects.append(user_obj) return user_objects - def create(self, text, repo_id, user_id, revision, f_path=None, - line_no=None): + def create(self, text, repo, user, revision=None, pull_request=None, + f_path=None, line_no=None, status_change=None): """ - Creates new comment for changeset + Creates new comment for changeset or pull request. + IF status_change is not none this comment is associated with a + status change of changeset or changesets associated with pull request :param text: - :param repo_id: - :param user_id: + :param repo: + :param user: :param revision: + :param pull_request: :param f_path: :param line_no: + :param status_change: """ + if not text: + return - if text: - repo = Repository.get(repo_id) + repo = self._get_repo(repo) + user = self._get_user(user) + comment = ChangesetComment() + comment.repo = repo + comment.author = user + comment.text = text + comment.f_path = f_path + comment.line_no = line_no + + if revision: cs = repo.scm_instance.get_changeset(revision) desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256)) author_email = cs.author_email - comment = ChangesetComment() - comment.repo = repo - comment.user_id = user_id comment.revision = revision - comment.text = text - comment.f_path = f_path - comment.line_no = line_no + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + comment.pull_request = pull_request + desc = pull_request.pull_request_id + else: + raise Exception('Please specify revision or pull_request_id') - self.sa.add(comment) - self.sa.flush() - # make notification - line = '' + self.sa.add(comment) + self.sa.flush() + + # make notification + line = '' + body = text + + #changeset + if revision: if line_no: line = _('on line %s') % line_no subj = safe_unicode( - h.link_to('Re commit: %(commit_desc)s %(line)s' % \ - {'commit_desc': desc, 'line': line}, + h.link_to('Re commit: %(desc)s %(line)s' % \ + {'desc': desc, 'line': line}, h.url('changeset_home', repo_name=repo.repo_name, revision=revision, anchor='comment-%s' % comment.comment_id, @@ -93,31 +118,51 @@ class ChangesetCommentsModel(BaseModel): ) ) ) - - body = text - + notification_type = Notification.TYPE_CHANGESET_COMMENT # get the current participants of this changeset recipients = ChangesetComment.get_users(revision=revision) - # add changeset author if it's in rhodecode system recipients += [User.get_by_email(author_email)] - - NotificationModel().create( - created_by=user_id, subject=subj, body=body, - recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT + #pull request + elif pull_request: + subj = safe_unicode( + h.link_to('Re pull request: %(desc)s %(line)s' % \ + {'desc': desc, 'line': line}, + h.url('pullrequest_show', + repo_name=pull_request.other_repo.repo_name, + pull_request_id=pull_request.pull_request_id, + anchor='comment-%s' % comment.comment_id, + qualified=True, + ) + ) ) - mention_recipients = set(self._extract_mentions(body))\ - .difference(recipients) - if mention_recipients: - subj = _('[Mention]') + ' ' + subj - NotificationModel().create( - created_by=user_id, subject=subj, body=body, - recipients=mention_recipients, - type_=Notification.TYPE_CHANGESET_COMMENT - ) + notification_type = Notification.TYPE_PULL_REQUEST_COMMENT + # get the current participants of this pull request + recipients = ChangesetComment.get_users(pull_request_id= + pull_request.pull_request_id) + # add pull request author + recipients += [pull_request.author] - return comment + # create notification objects, and emails + NotificationModel().create( + created_by=user, subject=subj, body=body, + recipients=recipients, type_=notification_type, + email_kwargs={'status_change': status_change} + ) + + mention_recipients = set(self._extract_mentions(body))\ + .difference(recipients) + if mention_recipients: + subj = _('[Mention]') + ' ' + subj + NotificationModel().create( + created_by=user, subject=subj, body=body, + recipients=mention_recipients, + type_=notification_type, + email_kwargs={'status_change': status_change} + ) + + return comment def delete(self, comment): """ @@ -130,21 +175,48 @@ class ChangesetCommentsModel(BaseModel): return comment - def get_comments(self, repo_id, revision): - return ChangesetComment.query()\ + def get_comments(self, repo_id, revision=None, pull_request=None): + """ + Get's main comments based on revision or pull_request_id + + :param repo_id: + :type repo_id: + :param revision: + :type revision: + :param pull_request: + :type pull_request: + """ + + q = ChangesetComment.query()\ .filter(ChangesetComment.repo_id == repo_id)\ - .filter(ChangesetComment.revision == revision)\ .filter(ChangesetComment.line_no == None)\ - .filter(ChangesetComment.f_path == None).all() + .filter(ChangesetComment.f_path == None) + if revision: + q = q.filter(ChangesetComment.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetComment.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request') + q = q.order_by(ChangesetComment.created_on) + return q.all() - def get_inline_comments(self, repo_id, revision): - comments = self.sa.query(ChangesetComment)\ + def get_inline_comments(self, repo_id, revision=None, pull_request=None): + q = self.sa.query(ChangesetComment)\ .filter(ChangesetComment.repo_id == repo_id)\ - .filter(ChangesetComment.revision == revision)\ .filter(ChangesetComment.line_no != None)\ .filter(ChangesetComment.f_path != None)\ .order_by(ChangesetComment.comment_id.asc())\ - .all() + + if revision: + q = q.filter(ChangesetComment.revision == revision) + elif pull_request: + pull_request = self.__get_pull_request(pull_request) + q = q.filter(ChangesetComment.pull_request == pull_request) + else: + raise Exception('Please specify revision or pull_request_id') + + comments = q.all() paths = defaultdict(lambda: defaultdict(list)) diff --git a/rhodecode/model/db.py b/rhodecode/model/db.py --- a/rhodecode/model/db.py +++ b/rhodecode/model/db.py @@ -27,12 +27,18 @@ import os import logging import datetime import traceback +import hashlib +import time from collections import defaultdict 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 @@ -45,9 +51,8 @@ from rhodecode.lib.compat import json from rhodecode.lib.caching_query import FromCache from rhodecode.model.meta import Base, Session -import hashlib - +URL_SEP = '/' log = logging.getLogger(__name__) #============================================================================== @@ -57,37 +62,6 @@ log = logging.getLogger(__name__) _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest() -class ModelSerializer(json.JSONEncoder): - """ - Simple Serializer for JSON, - - usage:: - - to make object customized for serialization implement a __json__ - method that will return a dict for serialization into json - - example:: - - class Task(object): - - def __init__(self, name, value): - self.name = name - self.value = value - - def __json__(self): - return dict(name=self.name, - value=self.value) - - """ - - def default(self, obj): - - if hasattr(obj, '__json__'): - return obj.__json__() - else: - return json.JSONEncoder.default(self, obj) - - class BaseModel(object): """ Base Model for all classess @@ -108,8 +82,13 @@ class BaseModel(object): d[k] = getattr(self, k) # also use __json__() if present to get additional fields - for k, val in getattr(self, '__json__', lambda: {})().iteritems(): - d[k] = val + _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): @@ -130,7 +109,7 @@ class BaseModel(object): @classmethod def query(cls): - return Session.query(cls) + return Session().query(cls) @classmethod def get(cls, id_): @@ -138,13 +117,21 @@ class BaseModel(object): return cls.query().get(id_) @classmethod + def get_or_404(cls, id_): + if id_: + 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) + Session().delete(obj) def __repr__(self): if hasattr(self, '__unicode__'): @@ -152,16 +139,17 @@ class BaseModel(object): return safe_str(self.__unicode__()) return '' % (self.__class__.__name__) + class RhodeCodeSetting(Base, BaseModel): __tablename__ = 'rhodecode_settings' __table_args__ = ( UniqueConstraint('app_settings_name'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'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(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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 @@ -195,9 +183,16 @@ class RhodeCodeSetting(Base, BaseModel): ) @classmethod - def get_by_name(cls, ldap_key): + def get_by_name(cls, key): return cls.query()\ - .filter(cls.app_settings_name == ldap_key).scalar() + .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): @@ -222,7 +217,7 @@ class RhodeCodeSetting(Base, BaseModel): .filter(cls.app_settings_name.startswith('ldap_')).all() fd = {} for row in ret: - fd.update({row.app_settings_name:row.app_settings_value}) + fd.update({row.app_settings_name: row.app_settings_value}) return fd @@ -231,71 +226,82 @@ class RhodeCodeUi(Base, BaseModel): __tablename__ = 'rhodecode_ui' __table_args__ = ( UniqueConstraint('ui_key'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) HOOK_UPDATE = 'changegroup.update' HOOK_REPO_SIZE = 'changegroup.repo_size' - HOOK_PUSH = 'pretxnchangegroup.push_logger' - HOOK_PULL = 'preoutgoing.pull_logger' + 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(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + ui_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) + 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_PULL])) + 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_PULL])) + 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).scalar() or cls() + 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) + Session().add(new_ui) class User(Base, BaseModel): __tablename__ = 'users' __table_args__ = ( UniqueConstraint('username'), UniqueConstraint('email'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + Index('u_username_idx', 'username'), + Index('u_email_idx', 'email'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) + DEFAULT_USER = 'default' + user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) - username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - active = Column("active", Boolean(), nullable=True, unique=None, default=None) + 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("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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', cascade='all') user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all') @@ -312,6 +318,8 @@ class User(Base, BaseModel): 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): @@ -322,21 +330,35 @@ class User(Base, BaseModel): 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.name, self.lastname) + return '%s %s' % (self.firstname, self.lastname) @property def full_name_or_username(self): - return ('%s %s' % (self.name, self.lastname) - if (self.name and self.lastname) else self.username) + 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.name, self.lastname, self.email) + return '%s %s <%s>' % (self.firstname, self.lastname, self.email) @property def short_contact(self): - return '%s %s' % (self.name, self.lastname) + return '%s %s' % (self.firstname, self.lastname) @property def is_admin(self): @@ -379,40 +401,105 @@ class User(Base, BaseModel): if cache: q = q.options(FromCache("sql_cache_short", - "get_api_key_%s" % email)) - return q.scalar() + "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) + 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): - return dict( - user_id=self.user_id, - first_name=self.name, - last_name=self.lastname, - email=self.email, + 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', + {'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=False, unique=None, default=None) repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True) - repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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 @@ -426,13 +513,14 @@ class UserLog(Base, BaseModel): class UsersGroup(Base, BaseModel): __tablename__ = 'users_groups' __table_args__ = ( - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'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(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + 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') @@ -464,11 +552,22 @@ class UsersGroup(Base, BaseModel): "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', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}, ) @@ -488,20 +587,24 @@ class Repository(Base, BaseModel): __tablename__ = 'repositories' __table_args__ = ( UniqueConstraint('repo_name'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + 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(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) - clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None) - repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg') + 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(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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) + 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) @@ -513,27 +616,58 @@ class Repository(Base, BaseModel): 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') + 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, + 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 '/' + return URL_SEP @classmethod def get_by_repo_name(cls, repo_name): - q = Session.query(cls).filter(cls.repo_name == 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) @@ -544,12 +678,26 @@ class Repository(Base, BaseModel): :param cls: """ - q = Session.query(RhodeCodeUi)\ + 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] @@ -580,7 +728,7 @@ class Repository(Base, BaseModel): Returns base full path for that repository means where it actually exists on a filesystem """ - q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == + 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 @@ -626,10 +774,26 @@ class Repository(Base, BaseModel): 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) + if ui_.ui_key == 'push_ssl': + # force set push_ssl requirement to False, rhodecode + # handles that + baseui.setconfig(ui_.ui_section, ui_.ui_key, False) return baseui @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 @@ -641,6 +805,39 @@ class Repository(Base, BaseModel): 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() + #========================================================================== # SCM PROPERTIES #========================================================================== @@ -648,6 +845,13 @@ class Repository(Base, BaseModel): 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 + @property def tip(self): return self.get_changeset('tip') @@ -660,7 +864,7 @@ class Repository(Base, BaseModel): def last_change(self): return self.scm_instance.last_change - def comments(self, revisions=None): + def get_comments(self, revisions=None): """ Returns comments for this repository grouped by revisions @@ -675,6 +879,39 @@ class Repository(Base, BaseModel): 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 #========================================================================== @@ -693,18 +930,27 @@ class Repository(Base, BaseModel): def scm_instance(self): return self.__get_instance() - @property - def scm_instance_cached(self): + 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') - inv = self.invalidate - if inv is not None: + + 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(inv.cache_key) + CacheInvalidation.set_valid(invalidate_repo.cache_key) return _c(rn) def __get_instance(self): @@ -738,15 +984,16 @@ class RepoGroup(Base, BaseModel): __table_args__ = ( UniqueConstraint('group_name', 'group_parent_id'), CheckConstraint('group_id != group_parent_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'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(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None) + 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(length=10000, convert_unicode=False, assert_unicode=None), 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') @@ -776,7 +1023,7 @@ class RepoGroup(Base, BaseModel): @classmethod def url_sep(cls): - return '/' + return URL_SEP @classmethod def get_by_group_name(cls, group_name, cache=False, case_insensitive=False): @@ -853,6 +1100,24 @@ class RepoGroup(Base, BaseModel): 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 @@ -867,12 +1132,55 @@ class RepoGroup(Base, BaseModel): class Permission(Base, BaseModel): __tablename__ = 'permissions' __table_args__ = ( - {'extend_existing': True, 'mysql_engine':'InnoDB', + 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(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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')>" % ( @@ -885,7 +1193,7 @@ class Permission(Base, BaseModel): @classmethod def get_default_perms(cls, default_user_id): - q = Session.query(UserRepoToPerm, Repository, cls)\ + 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) @@ -894,7 +1202,7 @@ class Permission(Base, BaseModel): @classmethod def get_default_group_perms(cls, default_user_id): - q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\ + 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) @@ -906,7 +1214,7 @@ class UserRepoToPerm(Base, BaseModel): __tablename__ = 'repo_to_perm' __table_args__ = ( UniqueConstraint('user_id', 'repository_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'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) @@ -924,7 +1232,7 @@ class UserRepoToPerm(Base, BaseModel): n.user = user n.repository = repository n.permission = permission - Session.add(n) + Session().add(n) return n def __unicode__(self): @@ -935,7 +1243,7 @@ class UserToPerm(Base, BaseModel): __tablename__ = 'user_to_perm' __table_args__ = ( UniqueConstraint('user_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'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) @@ -950,7 +1258,7 @@ class UsersGroupRepoToPerm(Base, BaseMod __tablename__ = 'users_group_repo_to_perm' __table_args__ = ( UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'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) @@ -968,7 +1276,7 @@ class UsersGroupRepoToPerm(Base, BaseMod n.users_group = users_group n.repository = repository n.permission = permission - Session.add(n) + Session().add(n) return n def __unicode__(self): @@ -979,7 +1287,7 @@ class UsersGroupToPerm(Base, BaseModel): __tablename__ = 'users_group_to_perm' __table_args__ = ( UniqueConstraint('users_group_id', 'permission_id',), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'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) @@ -994,7 +1302,7 @@ class UserRepoGroupToPerm(Base, BaseMode __tablename__ = 'user_repo_group_to_perm' __table_args__ = ( UniqueConstraint('user_id', 'group_id', 'permission_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) @@ -1012,7 +1320,7 @@ class UsersGroupRepoGroupToPerm(Base, Ba __tablename__ = 'users_group_repo_group_to_perm' __table_args__ = ( UniqueConstraint('users_group_id', 'group_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) @@ -1030,7 +1338,7 @@ class Statistics(Base, BaseModel): __tablename__ = 'statistics' __table_args__ = ( UniqueConstraint('repository_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True) @@ -1048,7 +1356,7 @@ class UserFollowing(Base, BaseModel): __table_args__ = ( UniqueConstraint('user_id', 'follows_repository_id'), UniqueConstraint('user_id', 'follows_user_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) @@ -1072,12 +1380,13 @@ class CacheInvalidation(Base, BaseModel) __tablename__ = 'cache_invalidation' __table_args__ = ( UniqueConstraint('cache_key'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + 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(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + 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=''): @@ -1088,6 +1397,7 @@ class CacheInvalidation(Base, BaseModel) def __unicode__(self): return u"<%s('%s:%s')>" % (self.__class__.__name__, self.cache_id, self.cache_key) + @classmethod def clear_cache(cls): cls.query().delete() @@ -1112,15 +1422,15 @@ class CacheInvalidation(Base, BaseModel) @classmethod def _get_or_create_key(cls, key, prefix, org_key): - inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar() + inv_obj = Session().query(cls).filter(cls.cache_key == key).scalar() if not inv_obj: try: inv_obj = CacheInvalidation(key, org_key) - Session.add(inv_obj) - Session.commit() + Session().add(inv_obj) + Session().commit() except Exception: log.error(traceback.format_exc()) - Session.rollback() + Session().rollback() return inv_obj @classmethod @@ -1148,7 +1458,7 @@ class CacheInvalidation(Base, BaseModel) """ key, _prefix, _org_key = cls._get_key(key) - inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all() + inv_objs = Session().query(cls).filter(cls.cache_args == _org_key).all() log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs), _org_key)) try: @@ -1156,11 +1466,11 @@ class CacheInvalidation(Base, BaseModel) if inv_obj: inv_obj.cache_active = False - Session.add(inv_obj) - Session.commit() + Session().add(inv_obj) + Session().commit() except Exception: log.error(traceback.format_exc()) - Session.rollback() + Session().rollback() @classmethod def set_valid(cls, key): @@ -1171,46 +1481,211 @@ class CacheInvalidation(Base, BaseModel) """ inv_obj = cls.get_by_key(key) inv_obj.cache_active = True - Session.add(inv_obj) - Session.commit() + 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__ = ( - {'extend_existing': True, 'mysql_engine':'InnoDB', + 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=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', Unicode(25000), nullable=False) - modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now) + 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): + def get_users(cls, revision=None, pull_request_id=None): """ - Returns user associated with this changesetComment. ie those + Returns user associated with this ChangesetComment. ie those who actually commented :param cls: :param revision: """ - return Session.query(User)\ - .filter(cls.revision == revision)\ - .join(ChangesetComment.author).all() + 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__ = ( - {'extend_existing': True, 'mysql_engine':'InnoDB', + Index('notification_type_idx', 'type'), + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}, ) @@ -1218,10 +1693,12 @@ class Notification(Base, BaseModel): 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', Unicode(50000), 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)) @@ -1234,7 +1711,7 @@ class Notification(Base, BaseModel): def recipients(self): return [x.user for x in UserNotification.query()\ .filter(UserNotification.notification == self)\ - .order_by(UserNotification.user).all()] + .order_by(UserNotification.user_id.asc()).all()] @classmethod def create(cls, created_by, subject, body, recipients, type_=None): @@ -1252,7 +1729,7 @@ class Notification(Base, BaseModel): assoc = UserNotification() assoc.notification = notification u.notifications.append(assoc) - Session.add(notification) + Session().add(notification) return notification @property @@ -1265,7 +1742,7 @@ class UserNotification(Base, BaseModel): __tablename__ = 'user_to_notification' __table_args__ = ( UniqueConstraint('user_id', 'notification_id'), - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'} ) user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True) @@ -1279,13 +1756,13 @@ class UserNotification(Base, BaseModel): def mark_as_read(self): self.read = True - Session.add(self) + Session().add(self) class DbMigrateVersion(Base, BaseModel): __tablename__ = 'db_migrate_version' __table_args__ = ( - {'extend_existing': True, 'mysql_engine':'InnoDB', + {'extend_existing': True, 'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'}, ) repository_id = Column('repository_id', String(250), primary_key=True) diff --git a/rhodecode/model/forms.py b/rhodecode/model/forms.py --- a/rhodecode/model/forms.py +++ b/rhodecode/model/forms.py @@ -19,573 +19,77 @@ list=[1,2,3,4,5] for SELECT use formencode.All(OneOf(list), Int()) """ -import os -import re import logging -import traceback import formencode from formencode import All -from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \ - Email, Bool, StringBoolean, Set from pylons.i18n.translation import _ -from webhelpers.pylonslib.secure_form import authentication_token -from rhodecode.config.routing import ADMIN_PREFIX -from rhodecode.lib.utils import repo_name_slug -from rhodecode.lib.auth import authenticate, get_crypt_password -from rhodecode.lib.exceptions import LdapImportError -from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository +from rhodecode.model import validators as v from rhodecode import BACKENDS log = logging.getLogger(__name__) -#this is needed to translate the messages using _() in validators -class State_obj(object): - _ = staticmethod(_) - - -#============================================================================== -# VALIDATORS -#============================================================================== -class ValidAuthToken(formencode.validators.FancyValidator): - messages = {'invalid_token': _('Token mismatch')} - - def validate_python(self, value, state): - - if value != authentication_token(): - raise formencode.Invalid( - self.message('invalid_token', - state, search_number=value), - value, - state - ) - - -def ValidUsername(edit, old_data): - class _ValidUsername(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - if value in ['default', 'new_user']: - raise formencode.Invalid(_('Invalid username'), value, state) - #check if user is unique - old_un = None - if edit: - old_un = User.get(old_data.get('user_id')).username - - if old_un != value or not edit: - if User.get_by_username(value, case_insensitive=True): - raise formencode.Invalid(_('This username already ' - 'exists') , value, state) - - if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: - raise formencode.Invalid( - _('Username may only contain alphanumeric characters ' - 'underscores, periods or dashes and must begin with ' - 'alphanumeric character'), - value, - state - ) - - return _ValidUsername - - -def ValidUsersGroup(edit, old_data): - - class _ValidUsersGroup(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - if value in ['default']: - raise formencode.Invalid(_('Invalid group name'), value, state) - #check if group is unique - old_ugname = None - if edit: - old_ugname = UsersGroup.get( - old_data.get('users_group_id')).users_group_name - - if old_ugname != value or not edit: - if UsersGroup.get_by_group_name(value, cache=False, - case_insensitive=True): - raise formencode.Invalid(_('This users group ' - 'already exists'), value, - state) - - if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: - raise formencode.Invalid( - _('RepoGroup name may only contain alphanumeric characters ' - 'underscores, periods or dashes and must begin with ' - 'alphanumeric character'), - value, - state - ) - - return _ValidUsersGroup - - -def ValidReposGroup(edit, old_data): - class _ValidReposGroup(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - # TODO WRITE VALIDATIONS - group_name = value.get('group_name') - group_parent_id = value.get('group_parent_id') - - # slugify repo group just in case :) - slug = repo_name_slug(group_name) - - # check for parent of self - parent_of_self = lambda: ( - old_data['group_id'] == int(group_parent_id) - if group_parent_id else False - ) - if edit and parent_of_self(): - e_dict = { - 'group_parent_id': _('Cannot assign this group as parent') - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - old_gname = None - if edit: - old_gname = RepoGroup.get(old_data.get('group_id')).group_name - - if old_gname != group_name or not edit: - - # check group - gr = RepoGroup.query()\ - .filter(RepoGroup.group_name == slug)\ - .filter(RepoGroup.group_parent_id == group_parent_id)\ - .scalar() - - if gr: - e_dict = { - 'group_name': _('This group already exists') - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - # check for same repo - repo = Repository.query()\ - .filter(Repository.repo_name == slug)\ - .scalar() - - if repo: - e_dict = { - 'group_name': _('Repository with this name already exists') - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - return _ValidReposGroup - - -class ValidPassword(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - if not value: - return - - if value.get('password'): - try: - value['password'] = get_crypt_password(value['password']) - except UnicodeEncodeError: - e_dict = {'password': _('Invalid characters in password')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - if value.get('password_confirmation'): - try: - value['password_confirmation'] = \ - get_crypt_password(value['password_confirmation']) - except UnicodeEncodeError: - e_dict = { - 'password_confirmation': _('Invalid characters in password') - } - raise formencode.Invalid('', value, state, error_dict=e_dict) - - if value.get('new_password'): - try: - value['new_password'] = \ - get_crypt_password(value['new_password']) - except UnicodeEncodeError: - e_dict = {'new_password': _('Invalid characters in password')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - return value - - -class ValidPasswordsMatch(formencode.validators.FancyValidator): - - def validate_python(self, value, state): - - pass_val = value.get('password') or value.get('new_password') - if pass_val != value['password_confirmation']: - e_dict = {'password_confirmation': - _('Passwords do not match')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - -class ValidAuth(formencode.validators.FancyValidator): - messages = { - 'invalid_password':_('invalid password'), - 'invalid_login':_('invalid user name'), - 'disabled_account':_('Your account is disabled') - } - - # error mapping - e_dict = {'username': messages['invalid_login'], - 'password': messages['invalid_password']} - e_dict_disable = {'username': messages['disabled_account']} - - def validate_python(self, value, state): - password = value['password'] - username = value['username'] - user = User.get_by_username(username) - - if authenticate(username, password): - return value - else: - if user and user.active is False: - log.warning('user %s is disabled' % username) - raise formencode.Invalid( - self.message('disabled_account', - state=State_obj), - value, state, - error_dict=self.e_dict_disable - ) - else: - log.warning('user %s failed to authenticate' % username) - raise formencode.Invalid( - self.message('invalid_password', - state=State_obj), value, state, - error_dict=self.e_dict - ) - - -class ValidRepoUser(formencode.validators.FancyValidator): - - def to_python(self, value, state): - try: - User.query().filter(User.active == True)\ - .filter(User.username == value).one() - except Exception: - raise formencode.Invalid(_('This username is not valid'), - value, state) - return value - - -def ValidRepoName(edit, old_data): - class _ValidRepoName(formencode.validators.FancyValidator): - def to_python(self, value, state): - - repo_name = value.get('repo_name') - - slug = repo_name_slug(repo_name) - if slug in [ADMIN_PREFIX, '']: - e_dict = {'repo_name': _('This repository name is disallowed')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - if value.get('repo_group'): - gr = RepoGroup.get(value.get('repo_group')) - group_path = gr.full_path - # value needs to be aware of group name in order to check - # db key This is an actual just the name to store in the - # database - repo_name_full = group_path + RepoGroup.url_sep() + repo_name - - else: - group_path = '' - repo_name_full = repo_name - - value['repo_name_full'] = repo_name_full - rename = old_data.get('repo_name') != repo_name_full - create = not edit - if rename or create: - - if group_path != '': - if Repository.get_by_repo_name(repo_name_full): - e_dict = { - 'repo_name': _('This repository already exists in ' - 'a group "%s"') % gr.group_name - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - elif RepoGroup.get_by_group_name(repo_name_full): - e_dict = { - 'repo_name': _('There is a group with this name ' - 'already "%s"') % repo_name_full - } - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - elif Repository.get_by_repo_name(repo_name_full): - e_dict = {'repo_name': _('This repository ' - 'already exists')} - raise formencode.Invalid('', value, state, - error_dict=e_dict) - - return value - - return _ValidRepoName - - -def ValidForkName(*args, **kwargs): - return ValidRepoName(*args, **kwargs) - - -def SlugifyName(): - class _SlugifyName(formencode.validators.FancyValidator): - - def to_python(self, value, state): - return repo_name_slug(value) - - return _SlugifyName - - -def ValidCloneUri(): - from rhodecode.lib.utils import make_ui - - def url_handler(repo_type, url, proto, ui=None): - if repo_type == 'hg': - from mercurial.httprepo import httprepository, httpsrepository - if proto == 'https': - httpsrepository(make_ui('db'), url).capabilities - elif proto == 'http': - httprepository(make_ui('db'), url).capabilities - elif repo_type == 'git': - #TODO: write a git url validator - pass - - class _ValidCloneUri(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - repo_type = value.get('repo_type') - url = value.get('clone_uri') - e_dict = {'clone_uri': _('invalid clone url')} - - if not url: - pass - elif url.startswith('https'): - try: - url_handler(repo_type, url, 'https', make_ui('db')) - except Exception: - log.error(traceback.format_exc()) - raise formencode.Invalid('', value, state, error_dict=e_dict) - elif url.startswith('http'): - try: - url_handler(repo_type, url, 'http', make_ui('db')) - except Exception: - log.error(traceback.format_exc()) - raise formencode.Invalid('', value, state, error_dict=e_dict) - else: - e_dict = {'clone_uri': _('Invalid clone url, provide a ' - 'valid clone http\s url')} - raise formencode.Invalid('', value, state, error_dict=e_dict) - - return value - - return _ValidCloneUri - - -def ValidForkType(old_data): - class _ValidForkType(formencode.validators.FancyValidator): - - def to_python(self, value, state): - if old_data['repo_type'] != value: - raise formencode.Invalid(_('Fork have to be the same ' - 'type as original'), value, state) - - return value - return _ValidForkType - - -def ValidPerms(type_='repo'): - if type_ == 'group': - EMPTY_PERM = 'group.none' - elif type_ == 'repo': - EMPTY_PERM = 'repository.none' - - class _ValidPerms(formencode.validators.FancyValidator): - messages = { - 'perm_new_member_name': - _('This username or users group name is not valid') - } - - def to_python(self, value, state): - perms_update = [] - perms_new = [] - # build a list of permission to update and new permission to create - for k, v in value.items(): - # means new added member to permissions - if k.startswith('perm_new_member'): - new_perm = value.get('perm_new_member', False) - new_member = value.get('perm_new_member_name', False) - new_type = value.get('perm_new_member_type') - - if new_member and new_perm: - if (new_member, new_perm, new_type) not in perms_new: - perms_new.append((new_member, new_perm, new_type)) - elif k.startswith('u_perm_') or k.startswith('g_perm_'): - member = k[7:] - t = {'u': 'user', - 'g': 'users_group' - }[k[0]] - if member == 'default': - if value.get('private'): - # set none for default when updating to private repo - v = EMPTY_PERM - perms_update.append((member, v, t)) - - value['perms_updates'] = perms_update - value['perms_new'] = perms_new - - # update permissions - for k, v, t in perms_new: - try: - if t is 'user': - self.user_db = User.query()\ - .filter(User.active == True)\ - .filter(User.username == k).one() - if t is 'users_group': - self.user_db = UsersGroup.query()\ - .filter(UsersGroup.users_group_active == True)\ - .filter(UsersGroup.users_group_name == k).one() - - except Exception: - msg = self.message('perm_new_member_name', - state=State_obj) - raise formencode.Invalid( - msg, value, state, error_dict={'perm_new_member_name': msg} - ) - return value - return _ValidPerms - - -class ValidSettings(formencode.validators.FancyValidator): - - def to_python(self, value, state): - # settings form can't edit user - if 'user' in value: - del['value']['user'] - return value - - -class ValidPath(formencode.validators.FancyValidator): - def to_python(self, value, state): - - if not os.path.isdir(value): - msg = _('This is not a valid path') - raise formencode.Invalid(msg, value, state, - error_dict={'paths_root_path': msg}) - return value - - -def UniqSystemEmail(old_data): - class _UniqSystemEmail(formencode.validators.FancyValidator): - def to_python(self, value, state): - value = value.lower() - if (old_data.get('email') or '').lower() != value: - user = User.get_by_email(value, case_insensitive=True) - if user: - raise formencode.Invalid( - _("This e-mail address is already taken"), value, state - ) - return value - - return _UniqSystemEmail - - -class ValidSystemEmail(formencode.validators.FancyValidator): - def to_python(self, value, state): - value = value.lower() - user = User.get_by_email(value, case_insensitive=True) - if user is None: - raise formencode.Invalid( - _("This e-mail address doesn't exist."), value, state - ) - - return value - - -class LdapLibValidator(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - try: - import ldap - except ImportError: - raise LdapImportError - return value - - -class AttrLoginValidator(formencode.validators.FancyValidator): - - def to_python(self, value, state): - - if not value or not isinstance(value, (str, unicode)): - raise formencode.Invalid( - _("The LDAP Login attribute of the CN must be specified - " - "this is the name of the attribute that is equivalent " - "to 'username'"), value, state - ) - - return value - - -#============================================================================== -# FORMS -#============================================================================== class LoginForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = UnicodeString( + username = v.UnicodeString( strip=True, min=1, not_empty=True, messages={ - 'empty': _('Please enter a login'), - 'tooShort': _('Enter a value %(min)i characters long or more')} + 'empty': _(u'Please enter a login'), + 'tooShort': _(u'Enter a value %(min)i characters long or more')} ) - password = UnicodeString( + password = v.UnicodeString( strip=False, min=3, not_empty=True, messages={ - 'empty': _('Please enter a password'), - 'tooShort': _('Enter %(min)i characters or more')} + 'empty': _(u'Please enter a password'), + 'tooShort': _(u'Enter %(min)i characters or more')} ) - remember = StringBoolean(if_missing=False) + remember = v.StringBoolean(if_missing=False) - chained_validators = [ValidAuth] + chained_validators = [v.ValidAuth()] def UserForm(edit=False, old_data={}): class _UserForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = All(UnicodeString(strip=True, min=1, not_empty=True), - ValidUsername(edit, old_data)) + username = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.ValidUsername(edit, old_data)) if edit: - new_password = All(UnicodeString(strip=False, min=6, not_empty=False)) - password_confirmation = All(UnicodeString(strip=False, min=6, - not_empty=False)) - admin = StringBoolean(if_missing=False) + new_password = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=False) + ) + password_confirmation = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=False), + ) + admin = v.StringBoolean(if_missing=False) else: - password = All(UnicodeString(strip=False, min=6, not_empty=True)) - password_confirmation = All(UnicodeString(strip=False, min=6, - not_empty=False)) + password = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=True) + ) + password_confirmation = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=False) + ) - active = StringBoolean(if_missing=False) - name = UnicodeString(strip=True, min=1, not_empty=False) - lastname = UnicodeString(strip=True, min=1, not_empty=False) - email = All(Email(not_empty=True), UniqSystemEmail(old_data)) + active = v.StringBoolean(if_missing=False) + firstname = v.UnicodeString(strip=True, min=1, not_empty=False) + lastname = v.UnicodeString(strip=True, min=1, not_empty=False) + email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data)) - chained_validators = [ValidPasswordsMatch, ValidPassword] + chained_validators = [v.ValidPasswordsMatch()] return _UserForm @@ -595,15 +99,18 @@ def UsersGroupForm(edit=False, old_data= allow_extra_fields = True filter_extra_fields = True - users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True), - ValidUsersGroup(edit, old_data)) + users_group_name = All( + v.UnicodeString(strip=True, min=1, not_empty=True), + v.ValidUsersGroup(edit, old_data) + ) - users_group_active = StringBoolean(if_missing=False) + users_group_active = v.StringBoolean(if_missing=False) if edit: - users_group_members = OneOf(available_members, hideList=False, - testValueList=True, - if_missing=None, not_empty=False) + users_group_members = v.OneOf( + available_members, hideList=False, testValueList=True, + if_missing=None, not_empty=False + ) return _UsersGroupForm @@ -613,15 +120,16 @@ def ReposGroupForm(edit=False, old_data= allow_extra_fields = True filter_extra_fields = False - group_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - group_description = UnicodeString(strip=True, min=1, + group_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + group_description = v.UnicodeString(strip=True, min=1, not_empty=True) - group_parent_id = OneOf(available_groups, hideList=False, + group_parent_id = v.OneOf(available_groups, hideList=False, testValueList=True, if_missing=None, not_empty=False) - - chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')] + enable_locking = v.StringBoolean(if_missing=False) + chained_validators = [v.ValidReposGroup(edit, old_data), + v.ValidPerms('group')] return _ReposGroupForm @@ -630,16 +138,24 @@ def RegisterForm(edit=False, old_data={} class _RegisterForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = All(ValidUsername(edit, old_data), - UnicodeString(strip=True, min=1, not_empty=True)) - password = All(UnicodeString(strip=False, min=6, not_empty=True)) - password_confirmation = All(UnicodeString(strip=False, min=6, not_empty=True)) - active = StringBoolean(if_missing=False) - name = UnicodeString(strip=True, min=1, not_empty=False) - lastname = UnicodeString(strip=True, min=1, not_empty=False) - email = All(Email(not_empty=True), UniqSystemEmail(old_data)) + username = All( + v.ValidUsername(edit, old_data), + v.UnicodeString(strip=True, min=1, not_empty=True) + ) + password = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=True) + ) + password_confirmation = All( + v.ValidPassword(), + v.UnicodeString(strip=False, min=6, not_empty=True) + ) + active = v.StringBoolean(if_missing=False) + firstname = v.UnicodeString(strip=True, min=1, not_empty=False) + lastname = v.UnicodeString(strip=True, min=1, not_empty=False) + email = All(v.Email(not_empty=True), v.UniqSystemEmail(old_data)) - chained_validators = [ValidPasswordsMatch, ValidPassword] + chained_validators = [v.ValidPasswordsMatch()] return _RegisterForm @@ -648,67 +164,71 @@ def PasswordResetForm(): class _PasswordResetForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - email = All(ValidSystemEmail(), Email(not_empty=True)) + email = All(v.ValidSystemEmail(), v.Email(not_empty=True)) return _PasswordResetForm def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), - repo_groups=[]): + repo_groups=[], landing_revs=[]): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False)) - repo_group = OneOf(repo_groups, hideList=True) - repo_type = OneOf(supported_backends) - description = UnicodeString(strip=True, min=1, not_empty=True) - private = StringBoolean(if_missing=False) - enable_statistics = StringBoolean(if_missing=False) - enable_downloads = StringBoolean(if_missing=False) + repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + clone_uri = All(v.UnicodeString(strip=True, min=1, not_empty=False)) + repo_group = v.OneOf(repo_groups, hideList=True) + repo_type = v.OneOf(supported_backends) + description = v.UnicodeString(strip=True, min=1, not_empty=False) + private = v.StringBoolean(if_missing=False) + enable_statistics = v.StringBoolean(if_missing=False) + enable_downloads = v.StringBoolean(if_missing=False) + enable_locking = v.StringBoolean(if_missing=False) + landing_rev = v.OneOf(landing_revs, hideList=True) if edit: #this is repo owner - user = All(UnicodeString(not_empty=True), ValidRepoUser) + user = All(v.UnicodeString(not_empty=True), v.ValidRepoUser()) - chained_validators = [ValidCloneUri()(), - ValidRepoName(edit, old_data), - ValidPerms()] + chained_validators = [v.ValidCloneUri(), + v.ValidRepoName(edit, old_data), + v.ValidPerms()] return _RepoForm def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), - repo_groups=[]): + repo_groups=[], landing_revs=[]): class _RepoForkForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - repo_group = OneOf(repo_groups, hideList=True) - repo_type = All(ValidForkType(old_data), OneOf(supported_backends)) - description = UnicodeString(strip=True, min=1, not_empty=True) - private = StringBoolean(if_missing=False) - copy_permissions = StringBoolean(if_missing=False) - update_after_clone = StringBoolean(if_missing=False) - fork_parent_id = UnicodeString() - chained_validators = [ValidForkName(edit, old_data)] + repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + repo_group = v.OneOf(repo_groups, hideList=True) + repo_type = All(v.ValidForkType(old_data), v.OneOf(supported_backends)) + description = v.UnicodeString(strip=True, min=1, not_empty=True) + private = v.StringBoolean(if_missing=False) + copy_permissions = v.StringBoolean(if_missing=False) + update_after_clone = v.StringBoolean(if_missing=False) + fork_parent_id = v.UnicodeString() + chained_validators = [v.ValidForkName(edit, old_data)] + landing_rev = v.OneOf(landing_revs, hideList=True) return _RepoForkForm -def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(), - repo_groups=[]): +def RepoSettingsForm(edit=False, old_data={}, + supported_backends=BACKENDS.keys(), repo_groups=[], + landing_revs=[]): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), - SlugifyName()) - description = UnicodeString(strip=True, min=1, not_empty=True) - repo_group = OneOf(repo_groups, hideList=True) - private = StringBoolean(if_missing=False) - - chained_validators = [ValidRepoName(edit, old_data), ValidPerms(), - ValidSettings] + repo_name = All(v.UnicodeString(strip=True, min=1, not_empty=True), + v.SlugifyName()) + description = v.UnicodeString(strip=True, min=1, not_empty=True) + repo_group = v.OneOf(repo_groups, hideList=True) + private = v.StringBoolean(if_missing=False) + landing_rev = v.OneOf(landing_revs, hideList=True) + chained_validators = [v.ValidRepoName(edit, old_data), v.ValidPerms(), + v.ValidSettings()] return _RepoForm @@ -716,58 +236,108 @@ def ApplicationSettingsForm(): class _ApplicationSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True) - rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True) - rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False) + rhodecode_title = v.UnicodeString(strip=True, min=1, not_empty=True) + rhodecode_realm = v.UnicodeString(strip=True, min=1, not_empty=True) + rhodecode_ga_code = v.UnicodeString(strip=True, min=1, not_empty=False) return _ApplicationSettingsForm +def ApplicationVisualisationForm(): + class _ApplicationVisualisationForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = False + rhodecode_show_public_icon = v.StringBoolean(if_missing=False) + rhodecode_show_private_icon = v.StringBoolean(if_missing=False) + rhodecode_stylify_metatags = v.StringBoolean(if_missing=False) + + return _ApplicationVisualisationForm + + def ApplicationUiSettingsForm(): class _ApplicationUiSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - web_push_ssl = OneOf(['true', 'false'], if_missing='false') - paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True)) - hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False) - hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False) - hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False) - hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False) + web_push_ssl = v.StringBoolean(if_missing=False) + paths_root_path = All( + v.ValidPath(), + v.UnicodeString(strip=True, min=1, not_empty=True) + ) + hooks_changegroup_update = v.StringBoolean(if_missing=False) + hooks_changegroup_repo_size = v.StringBoolean(if_missing=False) + hooks_changegroup_push_logger = v.StringBoolean(if_missing=False) + hooks_outgoing_pull_logger = v.StringBoolean(if_missing=False) + + extensions_largefiles = v.StringBoolean(if_missing=False) + extensions_hgsubversion = v.StringBoolean(if_missing=False) + extensions_hggit = v.StringBoolean(if_missing=False) return _ApplicationUiSettingsForm -def DefaultPermissionsForm(perms_choices, register_choices, create_choices): +def DefaultPermissionsForm(perms_choices, register_choices, create_choices, + fork_choices): class _DefaultPermissionsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - overwrite_default = StringBoolean(if_missing=False) - anonymous = OneOf(['True', 'False'], if_missing=False) - default_perm = OneOf(perms_choices) - default_register = OneOf(register_choices) - default_create = OneOf(create_choices) + overwrite_default = v.StringBoolean(if_missing=False) + anonymous = v.StringBoolean(if_missing=False) + default_perm = v.OneOf(perms_choices) + default_register = v.OneOf(register_choices) + default_create = v.OneOf(create_choices) + default_fork = v.OneOf(fork_choices) return _DefaultPermissionsForm -def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices): +def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, + tls_kind_choices): class _LdapSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True #pre_validators = [LdapLibValidator] - ldap_active = StringBoolean(if_missing=False) - ldap_host = UnicodeString(strip=True,) - ldap_port = Number(strip=True,) - ldap_tls_kind = OneOf(tls_kind_choices) - ldap_tls_reqcert = OneOf(tls_reqcert_choices) - ldap_dn_user = UnicodeString(strip=True,) - ldap_dn_pass = UnicodeString(strip=True,) - ldap_base_dn = UnicodeString(strip=True,) - ldap_filter = UnicodeString(strip=True,) - ldap_search_scope = OneOf(search_scope_choices) - ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,)) - ldap_attr_firstname = UnicodeString(strip=True,) - ldap_attr_lastname = UnicodeString(strip=True,) - ldap_attr_email = UnicodeString(strip=True,) + ldap_active = v.StringBoolean(if_missing=False) + ldap_host = v.UnicodeString(strip=True,) + ldap_port = v.Number(strip=True,) + ldap_tls_kind = v.OneOf(tls_kind_choices) + ldap_tls_reqcert = v.OneOf(tls_reqcert_choices) + ldap_dn_user = v.UnicodeString(strip=True,) + ldap_dn_pass = v.UnicodeString(strip=True,) + ldap_base_dn = v.UnicodeString(strip=True,) + ldap_filter = v.UnicodeString(strip=True,) + ldap_search_scope = v.OneOf(search_scope_choices) + ldap_attr_login = All( + v.AttrLoginValidator(), + v.UnicodeString(strip=True,) + ) + ldap_attr_firstname = v.UnicodeString(strip=True,) + ldap_attr_lastname = v.UnicodeString(strip=True,) + ldap_attr_email = v.UnicodeString(strip=True,) return _LdapSettingsForm + + +def UserExtraEmailForm(): + class _UserExtraEmailForm(formencode.Schema): + email = All(v.UniqSystemEmail(), v.Email) + + return _UserExtraEmailForm + + +def PullRequestForm(): + class _PullRequestForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = True + + user = v.UnicodeString(strip=True, required=True) + org_repo = v.UnicodeString(strip=True, required=True) + 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()(), v.UniqueList(not_empty=True)) + review_members = v.UniqueList(not_empty=True) + + pullrequest_title = v.UnicodeString(strip=True, required=True, min=3) + pullrequest_desc = v.UnicodeString(strip=True, required=False) + + return _PullRequestForm \ No newline at end of file diff --git a/rhodecode/model/notification.py b/rhodecode/model/notification.py --- a/rhodecode/model/notification.py +++ b/rhodecode/model/notification.py @@ -27,12 +27,10 @@ import os import logging import traceback -import datetime from pylons.i18n.translation import _ import rhodecode -from rhodecode.config.conf import DATETIME_FORMAT from rhodecode.lib import helpers as h from rhodecode.model import BaseModel from rhodecode.model.db import Notification, User, UserNotification @@ -42,8 +40,7 @@ log = logging.getLogger(__name__) class NotificationModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) + cls = Notification def __get_notification(self, notification): if isinstance(notification, Notification): @@ -77,12 +74,12 @@ class NotificationModel(BaseModel): if recipients and not getattr(recipients, '__iter__', False): raise Exception('recipients must be a list of iterable') - created_by_obj = self.__get_user(created_by) + created_by_obj = self._get_user(created_by) if recipients: recipients_objs = [] for u in recipients: - obj = self.__get_user(u) + obj = self._get_user(u) if obj: recipients_objs.append(obj) recipients_objs = set(recipients_objs) @@ -103,11 +100,15 @@ class NotificationModel(BaseModel): if with_email is False: return notif - # send email with notification - for rec in recipients_objs: + #don't send email to person who created this comment + rec_objs = set(recipients_objs).difference(set([created_by_obj])) + + # send email with notification to all other participants + for rec in rec_objs: email_subject = NotificationModel().make_description(notif, False) type_ = type_ email_body = body + ## this is passed into template kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)} kwargs.update(email_kwargs) email_body_html = EmailNotificationModel()\ @@ -122,7 +123,7 @@ class NotificationModel(BaseModel): # we don't want to remove actual notification just the assignment try: notification = self.__get_notification(notification) - user = self.__get_user(user) + user = self._get_user(user) if notification and user: obj = UserNotification.query()\ .filter(UserNotification.user == user)\ @@ -135,30 +136,73 @@ class NotificationModel(BaseModel): log.error(traceback.format_exc()) raise - def get_for_user(self, user): - user = self.__get_user(user) - return user.notifications + def get_for_user(self, user, filter_=None): + """ + Get mentions for given user, filter them if filter dict is given + + :param user: + :type user: + :param filter: + """ + user = self._get_user(user) + + q = UserNotification.query()\ + .filter(UserNotification.user == user)\ + .join((Notification, UserNotification.notification_id == + Notification.notification_id)) + + if filter_: + q = q.filter(Notification.type_.in_(filter_)) + + return q.all() - def mark_all_read_for_user(self, user): - user = self.__get_user(user) - UserNotification.query()\ - .filter(UserNotification.read==False)\ - .update({'read': True}) + def mark_read(self, user, notification): + try: + notification = self.__get_notification(notification) + user = self._get_user(user) + if notification and user: + obj = UserNotification.query()\ + .filter(UserNotification.user == user)\ + .filter(UserNotification.notification + == notification)\ + .one() + obj.read = True + self.sa.add(obj) + return True + except Exception: + log.error(traceback.format_exc()) + raise + + def mark_all_read_for_user(self, user, filter_=None): + user = self._get_user(user) + q = UserNotification.query()\ + .filter(UserNotification.user == user)\ + .filter(UserNotification.read == False)\ + .join((Notification, UserNotification.notification_id == + Notification.notification_id)) + if filter_: + q = q.filter(Notification.type_.in_(filter_)) + + # this is a little inefficient but sqlalchemy doesn't support + # update on joined tables :( + for obj in q.all(): + obj.read = True + self.sa.add(obj) def get_unread_cnt_for_user(self, user): - user = self.__get_user(user) + user = self._get_user(user) return UserNotification.query()\ .filter(UserNotification.read == False)\ .filter(UserNotification.user == user).count() def get_unread_for_user(self, user): - user = self.__get_user(user) + user = self._get_user(user) return [x.notification for x in UserNotification.query()\ .filter(UserNotification.read == False)\ .filter(UserNotification.user == user).all()] def get_user_notification(self, user, notification): - user = self.__get_user(user) + user = self._get_user(user) notification = self.__get_notification(notification) return UserNotification.query()\ @@ -170,20 +214,23 @@ class NotificationModel(BaseModel): Creates a human readable description based on properties of notification object """ - + #alias + _n = notification _map = { - notification.TYPE_CHANGESET_COMMENT: _('commented on commit'), - notification.TYPE_MESSAGE: _('sent message'), - notification.TYPE_MENTION: _('mentioned you'), - notification.TYPE_REGISTRATION: _('registered in RhodeCode') + _n.TYPE_CHANGESET_COMMENT: _('commented on commit'), + _n.TYPE_MESSAGE: _('sent message'), + _n.TYPE_MENTION: _('mentioned you'), + _n.TYPE_REGISTRATION: _('registered in RhodeCode'), + _n.TYPE_PULL_REQUEST: _('opened new pull request'), + _n.TYPE_PULL_REQUEST_COMMENT: _('commented on pull request') } - tmpl = "%(user)s %(action)s %(when)s" + # action == _map string + tmpl = "%(user)s %(action)s at %(when)s" if show_age: when = h.age(notification.created_on) else: - DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT) - when = DTF(notification.created_on) + when = h.fmt_date(notification.created_on) data = dict( user=notification.created_by_user.username, @@ -197,6 +244,7 @@ class EmailNotificationModel(BaseModel): TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT TYPE_PASSWORD_RESET = 'passoword_link' TYPE_REGISTRATION = Notification.TYPE_REGISTRATION + TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST TYPE_DEFAULT = 'default' def __init__(self): diff --git a/rhodecode/model/permission.py b/rhodecode/model/permission.py --- a/rhodecode/model/permission.py +++ b/rhodecode/model/permission.py @@ -31,7 +31,8 @@ from sqlalchemy.exc import DatabaseError from rhodecode.lib.caching_query import FromCache from rhodecode.model import BaseModel -from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm +from rhodecode.model.db import User, Permission, UserToPerm, UserRepoToPerm,\ + UserRepoGroupToPerm log = logging.getLogger(__name__) @@ -41,6 +42,8 @@ class PermissionModel(BaseModel): Permissions model for RhodeCode """ + cls = Permission + def get_permission(self, permission_id, cache=False): """ Get's permissions by id @@ -74,8 +77,8 @@ class PermissionModel(BaseModel): form_result['perm_user_name']).scalar() u2p = self.sa.query(UserToPerm).filter(UserToPerm.user == perm_user).all() - if len(u2p) != 3: - raise Exception('Defined: %s should be 3 permissions for default' + if len(u2p) != 4: + raise Exception('Defined: %s should be 4 permissions for default' ' user. This should not happen please verify' ' your database' % len(u2p)) @@ -87,23 +90,38 @@ class PermissionModel(BaseModel): form_result['default_perm']) self.sa.add(p) - if p.permission.permission_name.startswith('hg.register.'): + elif p.permission.permission_name.startswith('hg.register.'): p.permission = self.get_permission_by_name( form_result['default_register']) self.sa.add(p) - if p.permission.permission_name.startswith('hg.create.'): + elif p.permission.permission_name.startswith('hg.create.'): p.permission = self.get_permission_by_name( form_result['default_create']) self.sa.add(p) + elif p.permission.permission_name.startswith('hg.fork.'): + p.permission = self.get_permission_by_name( + form_result['default_fork']) + self.sa.add(p) + + _def_name = form_result['default_perm'].split('repository.')[-1] #stage 2 update all default permissions for repos if checked if form_result['overwrite_default'] == True: + _def = self.get_permission_by_name('repository.' + _def_name) + # repos for r2p in self.sa.query(UserRepoToPerm)\ - .filter(UserRepoToPerm.user == perm_user).all(): - r2p.permission = self.get_permission_by_name( - form_result['default_perm']) + .filter(UserRepoToPerm.user == perm_user)\ + .all(): + r2p.permission = _def self.sa.add(r2p) + # groups + _def = self.get_permission_by_name('group.' + _def_name) + for g2p in self.sa.query(UserRepoGroupToPerm)\ + .filter(UserRepoGroupToPerm.user == perm_user)\ + .all(): + g2p.permission = _def + self.sa.add(g2p) # stage 3 set anonymous access if perm_user.username == 'default': diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py new file mode 100644 --- /dev/null +++ b/rhodecode/model/pull_request.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +""" + rhodecode.model.pull_request + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + pull request model for RhodeCode + + :created_on: Jun 6, 2012 + :author: marcink + :copyright: (C) 2012-2012 Marcin Kuzminski + :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 . + +import logging +import binascii +import datetime + +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.notification import NotificationModel +from rhodecode.lib.utils2 import safe_unicode + +from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil + +log = logging.getLogger(__name__) + + +class PullRequestModel(BaseModel): + + cls = PullRequest + + def __get_pull_request(self, pull_request): + return self._get_instance(PullRequest, pull_request) + + def get_all(self, repo): + 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): + + created_by_user = self._get_user(created_by) + org_repo = self._get_repo(org_repo) + other_repo = self._get_repo(other_repo) + + new = PullRequest() + new.org_repo = org_repo + new.org_ref = org_ref + new.other_repo = other_repo + new.other_ref = other_ref + new.revisions = revisions + new.title = title + new.description = description + new.author = created_by_user + self.sa.add(new) + Session().flush() + #members + for member in reviewers: + _usr = self._get_user(member) + reviewer = PullRequestReviewers(_usr, new) + self.sa.add(reviewer) + + #notification to reviewers + notif = NotificationModel() + + subject = safe_unicode( + h.link_to( + _('%(user)s wants you to review pull request #%(pr_id)s') % \ + {'user': created_by_user.username, + 'pr_id': new.pull_request_id}, + h.url('pullrequest_show', repo_name=other_repo.repo_name, + pull_request_id=new.pull_request_id, + qualified=True, + ) + ) + ) + body = description + notif.create(created_by=created_by_user, subject=subject, body=body, + recipients=reviewers, + type_=Notification.TYPE_PULL_REQUEST,) + + return new + + def update_reviewers(self, pull_request, reviewers_ids): + reviewers_ids = set(reviewers_ids) + pull_request = self.__get_pull_request(pull_request) + current_reviewers = PullRequestReviewers.query()\ + .filter(PullRequestReviewers.pull_request== + pull_request)\ + .all() + current_reviewers_ids = set([x.user.user_id for x in current_reviewers]) + + to_add = reviewers_ids.difference(current_reviewers_ids) + to_remove = current_reviewers_ids.difference(reviewers_ids) + + log.debug("Adding %s reviewers" % to_add) + log.debug("Removing %s reviewers" % to_remove) + + for uid in to_add: + _usr = self._get_user(uid) + reviewer = PullRequestReviewers(_usr, pull_request) + self.sa.add(reviewer) + + for uid in to_remove: + reviewer = PullRequestReviewers.query()\ + .filter(PullRequestReviewers.user_id==uid, + PullRequestReviewers.pull_request==pull_request)\ + .scalar() + if reviewer: + self.sa.delete(reviewer) + + def delete(self, pull_request): + pull_request = self.__get_pull_request(pull_request) + Session().delete(pull_request) + + def close_pull_request(self, pull_request): + pull_request = self.__get_pull_request(pull_request) + pull_request.status = PullRequest.STATUS_CLOSED + pull_request.updated_on = datetime.datetime.now() + self.sa.add(pull_request) + + def _get_changesets(self, org_repo, org_ref, other_repo, other_ref, + discovery_data): + """ + Returns a list of changesets that are incoming from org_repo@org_ref + to other_repo@other_ref + + :param org_repo: + :type org_repo: + :param org_ref: + :type org_ref: + :param other_repo: + :type other_repo: + :param other_ref: + :type other_ref: + :param tmp: + :type tmp: + """ + changesets = [] + #case two independent repos + common, incoming, rheads = discovery_data + if org_repo != other_repo and incoming: + revs = org_repo._repo.changelog.findmissing(common, rheads) + + for cs in reversed(map(binascii.hexlify, revs)): + changesets.append(org_repo.get_changeset(cs)) + else: + _revset_predicates = { + 'branch': 'branch', + 'book': 'bookmark', + 'tag': 'tag', + 'rev': 'id', + } + + revs = [ + "ancestors(%s('%s')) and not ancestors(%s('%s'))" % ( + _revset_predicates[org_ref[0]], org_ref[1], + _revset_predicates[other_ref[0]], other_ref[1] + ) + ] + + out = scmutil.revrange(org_repo._repo, revs) + for cs in reversed(out): + changesets.append(org_repo.get_changeset(cs)) + + return changesets + + def _get_discovery(self, org_repo, org_ref, other_repo, other_ref): + """ + Get's mercurial discovery data used to calculate difference between + repos and refs + + :param org_repo: + :type org_repo: + :param org_ref: + :type org_ref: + :param other_repo: + :type other_repo: + :param other_ref: + :type other_ref: + """ + + _org_repo = org_repo._repo + org_rev_type, org_rev = org_ref + + _other_repo = other_repo._repo + other_rev_type, other_rev = other_ref + + log.debug('Doing discovery for %s@%s vs %s@%s' % ( + org_repo, org_ref, other_repo, other_ref) + ) + #log.debug('Filter heads are %s[%s]' % ('', org_ref[1])) + org_peer = localrepo.locallegacypeer(_org_repo.local()) + tmp = discovery.findcommonincoming( + repo=_other_repo, # other_repo we check for incoming + remote=org_peer, # org_repo source for incoming + heads=[_other_repo[other_rev].node(), + _org_repo[org_rev].node()], + force=False + ) + return tmp + + def get_compare_data(self, org_repo, org_ref, other_repo, other_ref): + """ + Returns a tuple of incomming changesets, and discoverydata cache + + :param org_repo: + :type org_repo: + :param org_ref: + :type org_ref: + :param other_repo: + :type other_repo: + :param other_ref: + :type other_ref: + """ + + if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)): + raise Exception('org_ref must be a two element list/tuple') + + if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)): + raise Exception('other_ref must be a two element list/tuple') + + discovery_data = self._get_discovery(org_repo.scm_instance, + org_ref, + other_repo.scm_instance, + other_ref) + cs_ranges = self._get_changesets(org_repo.scm_instance, + org_ref, + other_repo.scm_instance, + other_ref, + discovery_data) + + return cs_ranges, discovery_data diff --git a/rhodecode/model/repo.py b/rhodecode/model/repo.py --- a/rhodecode/model/repo.py +++ b/rhodecode/model/repo.py @@ -22,6 +22,7 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import with_statement import os import shutil import logging @@ -45,25 +46,17 @@ log = logging.getLogger(__name__) class RepoModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) + cls = Repository + URL_SEPARATOR = Repository.url_sep() def __get_users_group(self, users_group): return self._get_instance(UsersGroup, users_group, callback=UsersGroup.get_by_group_name) - def __get_repos_group(self, repos_group): + def _get_repos_group(self, repos_group): return self._get_instance(RepoGroup, repos_group, callback=RepoGroup.get_by_group_name) - def __get_repo(self, repository): - return self._get_instance(Repository, repository, - callback=Repository.get_by_repo_name) - - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - @LazyProperty def repos_path(self): """ @@ -83,7 +76,7 @@ class RepoModel(BaseModel): return repo.scalar() def get_repo(self, repository): - return self.__get_repo(repository) + return self._get_repo(repository) def get_by_repo_name(self, repo_name, cache=False): repo = self.sa.query(Repository)\ @@ -209,37 +202,45 @@ class RepoModel(BaseModel): log.error(traceback.format_exc()) raise - def create(self, form_data, cur_user, just_db=False, fork=False): + def create_repo(self, repo_name, repo_type, description, owner, + private=False, clone_uri=None, repos_group=None, + landing_rev='tip', just_db=False, fork_of=None, + copy_fork_permissions=False): + """ + Create repository + + """ from rhodecode.model.scm import ScmModel + owner = self._get_user(owner) + fork_of = self._get_repo(fork_of) + repos_group = self._get_repos_group(repos_group) try: - if fork: - fork_parent_id = form_data['fork_parent_id'] # repo name is just a name of repository # while repo_name_full is a full qualified name that is combined # with name and path of group - repo_name = form_data['repo_name'] - repo_name_full = form_data['repo_name_full'] + repo_name_full = repo_name + repo_name = repo_name.split(self.URL_SEPARATOR)[-1] new_repo = Repository() new_repo.enable_statistics = False + new_repo.repo_name = repo_name_full + new_repo.repo_type = repo_type + new_repo.user = owner + new_repo.group = repos_group + new_repo.description = description or repo_name + new_repo.private = private + new_repo.clone_uri = clone_uri + new_repo.landing_rev = landing_rev - for k, v in form_data.items(): - if k == 'repo_name': - v = repo_name_full - if k == 'repo_group': - k = 'group_id' - if k == 'description': - v = v or repo_name + if repos_group: + new_repo.enable_locking = repos_group.enable_locking - setattr(new_repo, k, v) - - if fork: - parent_repo = Repository.get(fork_parent_id) + if fork_of: + parent_repo = fork_of new_repo.fork = parent_repo - new_repo.user_id = cur_user.user_id self.sa.add(new_repo) def _create_default_perms(): @@ -251,7 +252,7 @@ class RepoModel(BaseModel): default = p.permission.permission_name break - default_perm = 'repository.none' if form_data['private'] else default + default_perm = 'repository.none' if private else default repo_to_perm.permission_id = self.sa.query(Permission)\ .filter(Permission.permission_name == default_perm)\ @@ -262,9 +263,9 @@ class RepoModel(BaseModel): self.sa.add(repo_to_perm) - if fork: - if form_data.get('copy_permissions'): - repo = Repository.get(fork_parent_id) + if fork_of: + if copy_fork_permissions: + repo = fork_of user_perms = UserRepoToPerm.query()\ .filter(UserRepoToPerm.repository == repo).all() group_perms = UsersGroupRepoToPerm.query()\ @@ -283,20 +284,45 @@ class RepoModel(BaseModel): _create_default_perms() if not just_db: - self.__create_repo(repo_name, form_data['repo_type'], - form_data['repo_group'], - form_data['clone_uri']) + self.__create_repo(repo_name, repo_type, + repos_group, + clone_uri) log_create_repository(new_repo.get_dict(), - created_by=cur_user.username) + created_by=owner.username) # now automatically start following this repository as owner ScmModel(self.sa).toggle_following_repo(new_repo.repo_id, - cur_user.user_id) + owner.user_id) return new_repo except: log.error(traceback.format_exc()) raise + def create(self, form_data, cur_user, just_db=False, fork=None): + """ + Backward compatibility function, just a wrapper on top of create_repo + + :param form_data: + :param cur_user: + :param just_db: + :param fork: + """ + + repo_name = form_data['repo_name_full'] + repo_type = form_data['repo_type'] + description = form_data['description'] + owner = cur_user + private = form_data['private'] + clone_uri = form_data.get('clone_uri') + repos_group = form_data['repo_group'] + landing_rev = form_data['landing_rev'] + copy_fork_permissions = form_data.get('copy_permissions') + fork_of = form_data.get('fork_parent_id') + return self.create_repo( + repo_name, repo_type, description, owner, private, clone_uri, + repos_group, landing_rev, just_db, fork_of, copy_fork_permissions + ) + def create_fork(self, form_data, cur_user): """ Simple wrapper into executing celery task for fork creation @@ -308,13 +334,14 @@ class RepoModel(BaseModel): run_task(tasks.create_repo_fork, form_data, cur_user) def delete(self, repo): - repo = self.__get_repo(repo) - try: - self.sa.delete(repo) - self.__delete_repo(repo) - except: - log.error(traceback.format_exc()) - raise + repo = self._get_repo(repo) + if repo: + try: + self.sa.delete(repo) + self.__delete_repo(repo) + except: + log.error(traceback.format_exc()) + raise def grant_user_permission(self, repo, user, perm): """ @@ -325,9 +352,9 @@ class RepoModel(BaseModel): :param user: Instance of User, user_id or username :param perm: Instance of Permission, or permission_name """ - user = self.__get_user(user) - repo = self.__get_repo(repo) - permission = self.__get_perm(perm) + user = self._get_user(user) + repo = self._get_repo(repo) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UserRepoToPerm)\ @@ -350,8 +377,8 @@ class RepoModel(BaseModel): :param user: Instance of User, user_id or username """ - user = self.__get_user(user) - repo = self.__get_repo(repo) + user = self._get_user(user) + repo = self._get_repo(repo) obj = self.sa.query(UserRepoToPerm)\ .filter(UserRepoToPerm.repository == repo)\ @@ -369,9 +396,9 @@ class RepoModel(BaseModel): or users group name :param perm: Instance of Permission, or permission_name """ - repo = self.__get_repo(repo) + repo = self._get_repo(repo) group_name = self.__get_users_group(group_name) - permission = self.__get_perm(perm) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UsersGroupRepoToPerm)\ @@ -396,7 +423,7 @@ class RepoModel(BaseModel): :param group_name: Instance of UserGroup, users_group_id, or users group name """ - repo = self.__get_repo(repo) + repo = self._get_repo(repo) group_name = self.__get_users_group(group_name) obj = self.sa.query(UsersGroupRepoToPerm)\ @@ -421,7 +448,7 @@ class RepoModel(BaseModel): log.error(traceback.format_exc()) raise - def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False): + def __create_repo(self, repo_name, alias, parent, clone_uri=False): """ makes repository on filesystem. It's group aware means it'll create a repository within a group, and alter the paths accordingly of @@ -433,11 +460,10 @@ class RepoModel(BaseModel): :param clone_uri: """ from rhodecode.lib.utils import is_valid_repo, is_valid_repos_group + from rhodecode.model.scm import ScmModel - if new_parent_id: - paths = RepoGroup.get(new_parent_id)\ - .full_path.split(RepoGroup.url_sep()) - new_parent_path = os.sep.join(paths) + if parent: + new_parent_path = os.sep.join(parent.full_path_splitted) else: new_parent_path = '' @@ -458,8 +484,14 @@ class RepoModel(BaseModel): ) ) backend = get_backend(alias) - - backend(repo_path, create=True, src_url=clone_uri) + if alias == 'hg': + backend(repo_path, create=True, src_url=clone_uri) + elif alias == 'git': + r = backend(repo_path, create=True, src_url=clone_uri, bare=True) + # add rhodecode hook into this repo + ScmModel().install_git_hook(repo=r) + else: + raise Exception('Undefined alias %s' % alias) def __rename_repo(self, old, new): """ @@ -489,11 +521,18 @@ class RepoModel(BaseModel): """ rm_path = os.path.join(self.repos_path, repo.repo_name) log.info("Removing %s" % (rm_path)) - # disable hg/git + # disable hg/git internal that it doesn't get detected as repo alias = repo.repo_type - shutil.move(os.path.join(rm_path, '.%s' % alias), - os.path.join(rm_path, 'rm__.%s' % alias)) + + bare = getattr(repo.scm_instance, 'bare', False) + + if not bare: + # skip this for bare git repos + shutil.move(os.path.join(rm_path, '.%s' % alias), + os.path.join(rm_path, 'rm__.%s' % alias)) # disable repo - _d = 'rm__%s__%s' % (datetime.now().strftime('%Y%m%d_%H%M%S_%f'), + _now = datetime.now() + _ms = str(_now.microsecond).rjust(6, '0') + _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms), repo.repo_name) shutil.move(rm_path, os.path.join(self.repos_path, _d)) diff --git a/rhodecode/model/repo_permission.py b/rhodecode/model/repo_permission.py --- a/rhodecode/model/repo_permission.py +++ b/rhodecode/model/repo_permission.py @@ -26,28 +26,19 @@ import logging from rhodecode.model import BaseModel -from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, Permission,\ - User, Repository +from rhodecode.model.db import UserRepoToPerm, UsersGroupRepoToPerm, \ + Permission log = logging.getLogger(__name__) class RepositoryPermissionModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - - def __get_repo(self, repository): - return self._get_instance(Repository, repository, - callback=Repository.get_by_repo_name) - - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) + cls = UserRepoToPerm def get_user_permission(self, repository, user): - repository = self.__get_repo(repository) - user = self.__get_user(user) + repository = self._get_repo(repository) + user = self._get_user(user) return UserRepoToPerm.query() \ .filter(UserRepoToPerm.user == user) \ diff --git a/rhodecode/model/repos_group.py b/rhodecode/model/repos_group.py --- a/rhodecode/model/repos_group.py +++ b/rhodecode/model/repos_group.py @@ -39,28 +39,23 @@ log = logging.getLogger(__name__) class ReposGroupModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) + cls = RepoGroup def __get_users_group(self, users_group): return self._get_instance(UsersGroup, users_group, callback=UsersGroup.get_by_group_name) - def __get_repos_group(self, repos_group): + def _get_repos_group(self, repos_group): return self._get_instance(RepoGroup, repos_group, callback=RepoGroup.get_by_group_name) - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - @LazyProperty def repos_path(self): """ Get's the repositories root path from database """ - q = RhodeCodeUi.get_by_key('/').one() + q = RhodeCodeUi.get_by_key('/') return q.ui_value def _create_default_perms(self, new_group): @@ -134,11 +129,11 @@ class ReposGroupModel(BaseModel): # delete only if that path really exists os.rmdir(rm_path) - def create(self, group_name, group_description, parent, just_db=False): + def create(self, group_name, group_description, parent=None, just_db=False): try: new_repos_group = RepoGroup() new_repos_group.group_description = group_description - new_repos_group.parent_group = self.__get_repos_group(parent) + new_repos_group.parent_group = self._get_repos_group(parent) new_repos_group.group_name = new_repos_group.get_new_name(group_name) self.sa.add(new_repos_group) @@ -188,11 +183,20 @@ class ReposGroupModel(BaseModel): repos_group.group_description = form_data['group_description'] repos_group.parent_group = RepoGroup.get(form_data['group_parent_id']) repos_group.group_parent_id = form_data['group_parent_id'] + repos_group.enable_locking = form_data['enable_locking'] repos_group.group_name = repos_group.get_new_name(form_data['group_name']) new_path = repos_group.full_path self.sa.add(repos_group) + # iterate over all members of this groups and set the locking ! + # this can be potentially heavy operation + + for obj in repos_group.recursive_groups_and_repos(): + #set the value from it's parent + obj.enable_locking = repos_group.enable_locking + self.sa.add(obj) + # we need to get all repositories from this new group and # rename them accordingly to new group path for r in repos_group.repositories: @@ -206,13 +210,13 @@ class ReposGroupModel(BaseModel): log.error(traceback.format_exc()) raise - def delete(self, users_group_id): + def delete(self, repos_group): + repos_group = self._get_repos_group(repos_group) try: - users_group = RepoGroup.get(users_group_id) - self.sa.delete(users_group) - self.__delete_group(users_group) + self.sa.delete(repos_group) + self.__delete_group(repos_group) except: - log.error(traceback.format_exc()) + log.exception('Error removing repos_group %s' % repos_group) raise def grant_user_permission(self, repos_group, user, perm): @@ -226,9 +230,9 @@ class ReposGroupModel(BaseModel): :param perm: Instance of Permission, or permission_name """ - repos_group = self.__get_repos_group(repos_group) - user = self.__get_user(user) - permission = self.__get_perm(perm) + repos_group = self._get_repos_group(repos_group) + user = self._get_user(user) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UserRepoGroupToPerm)\ @@ -252,8 +256,8 @@ class ReposGroupModel(BaseModel): :param user: Instance of User, user_id or username """ - repos_group = self.__get_repos_group(repos_group) - user = self.__get_user(user) + repos_group = self._get_repos_group(repos_group) + user = self._get_user(user) obj = self.sa.query(UserRepoGroupToPerm)\ .filter(UserRepoGroupToPerm.user == user)\ @@ -272,9 +276,9 @@ class ReposGroupModel(BaseModel): or users group name :param perm: Instance of Permission, or permission_name """ - repos_group = self.__get_repos_group(repos_group) + repos_group = self._get_repos_group(repos_group) group_name = self.__get_users_group(group_name) - permission = self.__get_perm(perm) + permission = self._get_perm(perm) # check if we have that permission already obj = self.sa.query(UsersGroupRepoGroupToPerm)\ @@ -300,7 +304,7 @@ class ReposGroupModel(BaseModel): :param group_name: Instance of UserGroup, users_group_id, or users group name """ - repos_group = self.__get_repos_group(repos_group) + repos_group = self._get_repos_group(repos_group) group_name = self.__get_users_group(group_name) obj = self.sa.query(UsersGroupRepoGroupToPerm)\ diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -22,26 +22,35 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from __future__ import with_statement import os +import re import time import traceback import logging import cStringIO +import pkg_resources +from os.path import dirname as dn, join as jn +from sqlalchemy import func +from pylons.i18n.translation import _ + +import rhodecode from rhodecode.lib.vcs import get_backend from rhodecode.lib.vcs.exceptions import RepositoryError from rhodecode.lib.vcs.utils.lazy import LazyProperty from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.backends.base import EmptyChangeset from rhodecode import BACKENDS from rhodecode.lib import helpers as h from rhodecode.lib.utils2 import safe_str, safe_unicode from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \ - action_logger, EmptyChangeset, REMOVED_REPO_PAT + action_logger, REMOVED_REPO_PAT from rhodecode.model import BaseModel from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \ - UserFollowing, UserLog, User, RepoGroup + UserFollowing, UserLog, User, RepoGroup, PullRequest log = logging.getLogger(__name__) @@ -63,6 +72,10 @@ class RepoTemp(object): class CachedRepoList(object): + """ + Cached repo list, uses in-memory cache after initialization, that is + super fast + """ def __init__(self, db_repo_list, repos_path, order_by=None): self.db_repo_list = db_repo_list @@ -77,8 +90,12 @@ class CachedRepoList(object): return '<%s (%s)>' % (self.__class__.__name__, self.__len__()) def __iter__(self): + # pre-propagated cache_map to save executing select statements + # for each repo + cache_map = CacheInvalidation.get_cache_map() + for dbr in self.db_repo_list: - scmr = dbr.scm_instance_cached + scmr = dbr.scm_instance_cached(cache_map) # check permission at this level if not HasRepoPermissionAny( 'repository.read', 'repository.write', 'repository.admin' @@ -99,7 +116,7 @@ class CachedRepoList(object): tmp_d['name'] = dbr.repo_name tmp_d['name_sort'] = tmp_d['name'].lower() tmp_d['description'] = dbr.description - tmp_d['description_sort'] = tmp_d['description'] + tmp_d['description_sort'] = tmp_d['description'].lower() tmp_d['last_change'] = last_change tmp_d['last_change_sort'] = time.mktime(last_change.timetuple()) tmp_d['tip'] = tip.raw_id @@ -116,6 +133,29 @@ class CachedRepoList(object): yield tmp_d +class SimpleCachedRepoList(CachedRepoList): + """ + Lighter version of CachedRepoList without the scm initialisation + """ + + def __iter__(self): + for dbr in self.db_repo_list: + # check permission at this level + if not HasRepoPermissionAny( + 'repository.read', 'repository.write', 'repository.admin' + )(dbr.repo_name, 'get repo check'): + continue + + tmp_d = {} + tmp_d['name'] = dbr.repo_name + tmp_d['name_sort'] = tmp_d['name'].lower() + tmp_d['description'] = dbr.description + tmp_d['description_sort'] = tmp_d['description'].lower() + tmp_d['dbrepo'] = dbr.get_dict() + tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {} + yield tmp_d + + class GroupList(object): def __init__(self, db_repo_group_list): @@ -147,7 +187,7 @@ class ScmModel(BaseModel): cls = Repository if isinstance(instance, cls): return instance - elif isinstance(instance, int) or str(instance).isdigit(): + elif isinstance(instance, int) or safe_str(instance).isdigit(): return cls.get(instance) elif isinstance(instance, basestring): return cls.get_by_repo_name(instance) @@ -208,21 +248,29 @@ class ScmModel(BaseModel): return repos - def get_repos(self, all_repos=None, sort_key=None): + def get_repos(self, all_repos=None, sort_key=None, simple=False): """ Get all repos from db and for each repo create it's backend instance and fill that backed with information from database :param all_repos: list of repository names as strings give specific repositories list, good for filtering + + :param sort_key: initial sorting of repos + :param simple: use SimpleCachedList - one without the SCM info """ if all_repos is None: all_repos = self.sa.query(Repository)\ .filter(Repository.group_id == None)\ - .order_by(Repository.repo_name).all() - - repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path, - order_by=sort_key) + .order_by(func.lower(Repository.repo_name)).all() + if simple: + repo_iter = SimpleCachedRepoList(all_repos, + repos_path=self.repos_path, + order_by=sort_key) + else: + repo_iter = CachedRepoList(all_repos, + repos_path=self.repos_path, + order_by=sort_key) return repo_iter @@ -314,29 +362,33 @@ class ScmModel(BaseModel): return f is not None - def get_followers(self, repo_id): - if not isinstance(repo_id, int): - repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') + def get_followers(self, repo): + repo = self._get_repo(repo) return self.sa.query(UserFollowing)\ - .filter(UserFollowing.follows_repo_id == repo_id).count() + .filter(UserFollowing.follows_repository == repo).count() - def get_forks(self, repo_id): - if not isinstance(repo_id, int): - repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id') + def get_forks(self, repo): + repo = self._get_repo(repo) + return self.sa.query(Repository)\ + .filter(Repository.fork == repo).count() - return self.sa.query(Repository)\ - .filter(Repository.fork_id == repo_id).count() + def get_pull_requests(self, repo): + repo = self._get_repo(repo) + return self.sa.query(PullRequest)\ + .filter(PullRequest.other_repo == repo).count() def mark_as_fork(self, repo, fork, user): repo = self.__get_repo(repo) fork = self.__get_repo(fork) + if fork and repo.repo_id == fork.repo_id: + raise Exception("Cannot set repository as fork of itself") repo.fork = fork self.sa.add(repo) return repo - def pull_changes(self, repo_name, username): - dbrepo = Repository.get_by_repo_name(repo_name) + def pull_changes(self, repo, username): + dbrepo = self.__get_repo(repo) clone_uri = dbrepo.clone_uri if not clone_uri: raise Exception("This repository doesn't have a clone uri") @@ -347,22 +399,28 @@ class ScmModel(BaseModel): 'ip': '', 'username': username, 'action': 'push_remote', - 'repository': repo_name, + 'repository': dbrepo.repo_name, 'scm': repo.alias, } + Repository.inject_ui(repo, extras=extras) - # 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) - - repo.pull(clone_uri) - self.mark_for_invalidation(repo_name) + if repo.alias == 'git': + repo.fetch(clone_uri) + else: + repo.pull(clone_uri) + self.mark_for_invalidation(dbrepo.repo_name) except: log.error(traceback.format_exc()) raise def commit_change(self, repo, repo_name, cs, user, author, message, content, f_path): + """ + Commits changes + + :param repo: SCM instance + + """ if repo.alias == 'hg': from rhodecode.lib.vcs.backends.hg import \ @@ -385,12 +443,10 @@ class ScmModel(BaseModel): author=author, parents=[cs], branch=cs.branch) - new_cs = tip.short_id - action = 'push_local:%s' % new_cs - + action = 'push_local:%s' % tip.raw_id action_logger(user, action, repo_name) - self.mark_for_invalidation(repo_name) + return tip def create_node(self, repo, repo_name, cs, user, author, message, content, f_path): @@ -425,12 +481,11 @@ class ScmModel(BaseModel): tip = m.commit(message=message, author=author, parents=parents, branch=cs.branch) - new_cs = tip.short_id - action = 'push_local:%s' % new_cs + action = 'push_local:%s' % tip.raw_id action_logger(user, action, repo_name) - self.mark_for_invalidation(repo_name) + return tip def get_nodes(self, repo_name, revision, root_path='/', flat=True): """ @@ -464,3 +519,93 @@ class ScmModel(BaseModel): def get_unread_journal(self): return self.sa.query(UserLog).count() + + def get_repo_landing_revs(self, repo=None): + """ + Generates select option with tags branches and bookmarks (for hg only) + grouped by type + + :param repo: + :type repo: + """ + + hist_l = [] + choices = [] + repo = self.__get_repo(repo) + hist_l.append(['tip', _('latest tip')]) + choices.append('tip') + if not repo: + return choices, hist_l + + repo = repo.scm_instance + + branches_group = ([(k, k) for k, v in + repo.branches.iteritems()], _("Branches")) + hist_l.append(branches_group) + choices.extend([x[0] for x in branches_group[0]]) + + if repo.alias == 'hg': + bookmarks_group = ([(k, k) for k, v in + repo.bookmarks.iteritems()], _("Bookmarks")) + hist_l.append(bookmarks_group) + choices.extend([x[0] for x in bookmarks_group[0]]) + + tags_group = ([(k, k) for k, v in + repo.tags.iteritems()], _("Tags")) + hist_l.append(tags_group) + choices.extend([x[0] for x in tags_group[0]]) + + return choices, hist_l + + def install_git_hook(self, repo, force_create=False): + """ + Creates a rhodecode hook inside a git repository + + :param repo: Instance of VCS repo + :param force_create: Create even if same name hook exists + """ + + loc = jn(repo.path, 'hooks') + if not repo.bare: + loc = jn(repo.path, '.git', 'hooks') + if not os.path.isdir(loc): + os.makedirs(loc) + + tmpl_post = pkg_resources.resource_string( + 'rhodecode', jn('config', 'post_receive_tmpl.py') + ) + tmpl_pre = pkg_resources.resource_string( + 'rhodecode', jn('config', 'pre_receive_tmpl.py') + ) + + for h_type, tmpl in [('pre', tmpl_pre), ('post', tmpl_post)]: + _hook_file = jn(loc, '%s-receive' % h_type) + _rhodecode_hook = False + log.debug('Installing git hook in repo %s' % repo) + if os.path.exists(_hook_file): + # let's take a look at this hook, maybe it's rhodecode ? + log.debug('hook exists, checking if it is from rhodecode') + _HOOK_VER_PAT = re.compile(r'^RC_HOOK_VER') + with open(_hook_file, 'rb') as f: + data = f.read() + matches = re.compile(r'(?:%s)\s*=\s*(.*)' + % 'RC_HOOK_VER').search(data) + if matches: + try: + ver = matches.groups()[0] + log.debug('got %s it is rhodecode' % (ver)) + _rhodecode_hook = True + except: + log.error(traceback.format_exc()) + else: + # there is no hook in this dir, so we want to create one + _rhodecode_hook = True + + if _rhodecode_hook or force_create: + log.debug('writing %s hook file !' % h_type) + with open(_hook_file, 'wb') as f: + tmpl = tmpl.replace('_TMPL_', rhodecode.__version__) + f.write(tmpl) + os.chmod(_hook_file, 0755) + else: + log.debug('skipping writing hook file') diff --git a/rhodecode/model/user.py b/rhodecode/model/user.py --- a/rhodecode/model/user.py +++ b/rhodecode/model/user.py @@ -25,48 +25,31 @@ import logging import traceback - +import itertools from pylons import url from pylons.i18n.translation import _ +from sqlalchemy.exc import DatabaseError +from sqlalchemy.orm import joinedload + from rhodecode.lib.utils2 import safe_unicode, generate_api_key from rhodecode.lib.caching_query import FromCache - from rhodecode.model import BaseModel from rhodecode.model.db import User, UserRepoToPerm, Repository, Permission, \ UserToPerm, UsersGroupRepoToPerm, UsersGroupToPerm, UsersGroupMember, \ - Notification, RepoGroup, UserRepoGroupToPerm, UsersGroup,\ - UsersGroupRepoGroupToPerm + Notification, RepoGroup, UserRepoGroupToPerm, UsersGroupRepoGroupToPerm, \ + UserEmailMap from rhodecode.lib.exceptions import DefaultUserException, \ UserOwnsReposException -from sqlalchemy.exc import DatabaseError - -from sqlalchemy.orm import joinedload log = logging.getLogger(__name__) - -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, -} +PERM_WEIGHTS = Permission.PERM_WEIGHTS class UserModel(BaseModel): - - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) - - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) + cls = User def get(self, user_id, cache=False): user = self.sa.query(User) @@ -76,7 +59,7 @@ class UserModel(BaseModel): return user.get(user_id) def get_user(self, user): - return self.__get_user(user) + return self._get_user(user) def get_by_username(self, username, cache=False, case_insensitive=False): @@ -90,13 +73,21 @@ class UserModel(BaseModel): "get_user_%s" % username)) return user.scalar() + def get_by_email(self, email, cache=False, case_insensitive=False): + return User.get_by_email(email, case_insensitive, cache) + def get_by_api_key(self, api_key, cache=False): return User.get_by_api_key(api_key, cache) def create(self, form_data): + from rhodecode.lib.auth import get_crypt_password try: new_user = User() for k, v in form_data.items(): + if k == 'password': + v = get_crypt_password(v) + if k == 'firstname': + k = 'name' setattr(new_user, k, v) new_user.api_key = generate_api_key(form_data['username']) @@ -106,8 +97,8 @@ class UserModel(BaseModel): log.error(traceback.format_exc()) raise - def create_or_update(self, username, password, email, name, lastname, - active=True, admin=False, ldap_dn=None): + def create_or_update(self, username, password, email, firstname='', + lastname='', active=True, admin=False, ldap_dn=None): """ Creates a new instance if not found, or updates current one @@ -115,7 +106,7 @@ class UserModel(BaseModel): :param password: :param email: :param active: - :param name: + :param firstname: :param lastname: :param active: :param admin: @@ -129,19 +120,23 @@ class UserModel(BaseModel): if user is None: log.debug('creating new user %s' % username) new_user = User() + edit = False else: log.debug('updating user %s' % username) new_user = user + edit = True try: new_user.username = username new_user.admin = admin - new_user.password = get_crypt_password(password) - new_user.api_key = generate_api_key(username) + # set password only if creating an user or password is changed + if edit is False or user.password != password: + new_user.password = get_crypt_password(password) + new_user.api_key = generate_api_key(username) new_user.email = email new_user.active = active new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None - new_user.name = name + new_user.name = firstname new_user.lastname = lastname self.sa.add(new_user) return new_user @@ -252,6 +247,7 @@ class UserModel(BaseModel): raise def update(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': @@ -260,29 +256,56 @@ class UserModel(BaseModel): " crucial for entire application")) for k, v in form_data.items(): - if k == 'new_password' and v != '': - user.password = v + 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' setattr(user, k, v) + self.sa.add(user) + except: + log.error(traceback.format_exc()) + raise + def update_user(self, user, **kwargs): + from rhodecode.lib.auth import get_crypt_password + try: + user = self._get_user(user) + if user.username == 'default': + raise DefaultUserException( + _("You can't Edit this user since it's" + " crucial for entire application") + ) + + for k, v in kwargs.items(): + if k == 'password' and v: + v = get_crypt_password(v) + user.api_key = generate_api_key(user.username) + + setattr(user, k, v) self.sa.add(user) + return user except: 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")) + _("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 = v + 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) @@ -292,7 +315,7 @@ class UserModel(BaseModel): raise def delete(self, user): - user = self.__get_user(user) + user = self._get_user(user) try: if user.username == 'default': @@ -399,11 +422,11 @@ class UserModel(BaseModel): return user #================================================================== - # set default permissions first for repositories and groups + # SET DEFAULTS GLOBAL, REPOS, REPOS GROUPS #================================================================== uid = user.user_id - # default global permissions + # default global permissions taken fron the default user default_global_perms = self.sa.query(UserToPerm)\ .filter(UserToPerm.user_id == default_user_id) @@ -431,25 +454,89 @@ class UserModel(BaseModel): p = perm.Permission.permission_name user.permissions[GK][rg_k] = p - #================================================================== - # overwrite defaults with user permissions if any found - #================================================================== + #====================================================================== + # !! OVERRIDE GLOBALS !! with user permissions if any found + #====================================================================== + # those can be configured from groups or users explicitly + _configurable = set(['hg.fork.none', 'hg.fork.repository', + 'hg.create.none', 'hg.create.repository']) - # user global permissions + # USER GROUPS comes first + # users group global permissions + user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\ + .options(joinedload(UsersGroupToPerm.permission))\ + .join((UsersGroupMember, UsersGroupToPerm.users_group_id == + UsersGroupMember.users_group_id))\ + .filter(UsersGroupMember.user_id == uid)\ + .order_by(UsersGroupToPerm.users_group_id)\ + .all() + #need to group here by groups since user can be in more than one group + _grouped = [[x, list(y)] for x, y in + itertools.groupby(user_perms_from_users_groups, + lambda x:x.users_group)] + for gr, perms in _grouped: + # since user can be in multiple groups iterate over them and + # select the lowest permissions first (more explicit) + ##TODO: do this^^ + if not gr.inherit_default_permissions: + # NEED TO IGNORE all configurable permissions and + # replace them with explicitly set + user.permissions[GLOBAL] = user.permissions[GLOBAL]\ + .difference(_configurable) + for perm in perms: + user.permissions[GLOBAL].add(perm.permission.permission_name) + + # user specific global permissions user_perms = self.sa.query(UserToPerm)\ .options(joinedload(UserToPerm.permission))\ .filter(UserToPerm.user_id == uid).all() - for perm in user_perms: - user.permissions[GLOBAL].add(perm.permission.permission_name) + if not user.inherit_default_permissions: + # NEED TO IGNORE all configurable permissions and + # replace them with explicitly set + user.permissions[GLOBAL] = user.permissions[GLOBAL]\ + .difference(_configurable) + + for perm in user_perms: + user.permissions[GLOBAL].add(perm.permission.permission_name) + + #====================================================================== + # !! REPO PERMISSIONS !! + #====================================================================== + #====================================================================== + # check if user is part of user groups for this repository and + # fill in (or NOT replace with higher `or 1` permissions + #====================================================================== + # users group for repositories permissions + user_repo_perms_from_users_groups = \ + self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\ + .join((Repository, UsersGroupRepoToPerm.repository_id == + Repository.repo_id))\ + .join((Permission, UsersGroupRepoToPerm.permission_id == + Permission.permission_id))\ + .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == + UsersGroupMember.users_group_id))\ + .filter(UsersGroupMember.user_id == uid)\ + .all() + + for perm in user_repo_perms_from_users_groups: + r_k = perm.UsersGroupRepoToPerm.repository.repo_name + p = perm.Permission.permission_name + cur_perm = user.permissions[RK][r_k] + # overwrite permission only if it's greater than permission + # given from other sources + if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm] or 1: # disable check + user.permissions[RK][r_k] = p # user explicit permissions for repositories user_repo_perms = \ self.sa.query(UserRepoToPerm, Permission, Repository)\ - .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\ - .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\ - .filter(UserRepoToPerm.user_id == uid)\ - .all() + .join((Repository, UserRepoToPerm.repository_id == + Repository.repo_id))\ + .join((Permission, UserRepoToPerm.permission_id == + Permission.permission_id))\ + .filter(UserRepoToPerm.user_id == uid)\ + .all() for perm in user_repo_perms: # set admin if owner @@ -460,40 +547,6 @@ class UserModel(BaseModel): p = perm.Permission.permission_name user.permissions[RK][r_k] = p - # USER GROUP - #================================================================== - # check if user is part of user groups for this repository and - # fill in (or replace with higher) permissions - #================================================================== - - # users group global - user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\ - .options(joinedload(UsersGroupToPerm.permission))\ - .join((UsersGroupMember, UsersGroupToPerm.users_group_id == - UsersGroupMember.users_group_id))\ - .filter(UsersGroupMember.user_id == uid).all() - - for perm in user_perms_from_users_groups: - user.permissions[GLOBAL].add(perm.permission.permission_name) - - # users group for repositories permissions - user_repo_perms_from_users_groups = \ - self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\ - .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\ - .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\ - .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\ - .filter(UsersGroupMember.user_id == uid)\ - .all() - - for perm in user_repo_perms_from_users_groups: - r_k = perm.UsersGroupRepoToPerm.repository.repo_name - p = perm.Permission.permission_name - cur_perm = user.permissions[RK][r_k] - # overwrite permission only if it's greater than permission - # given from other sources - if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]: - user.permissions[RK][r_k] = p - # REPO GROUP #================================================================== # get access for this user for repos group and override defaults @@ -541,11 +594,8 @@ class UserModel(BaseModel): return user def has_perm(self, user, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class ' - 'got %s instead' % type(perm)) - - user = self.__get_user(user) + perm = self._get_perm(perm) + user = self._get_user(user) return UserToPerm.query().filter(UserToPerm.user == user)\ .filter(UserToPerm.permission == perm).scalar() is not None @@ -557,8 +607,8 @@ class UserModel(BaseModel): :param user: :param perm: """ - user = self.__get_user(user) - perm = self.__get_perm(perm) + user = self._get_user(user) + perm = self._get_perm(perm) # if this permission is already granted skip it _perm = UserToPerm.query()\ .filter(UserToPerm.user == user)\ @@ -578,8 +628,8 @@ class UserModel(BaseModel): :param user: :param perm: """ - user = self.__get_user(user) - perm = self.__get_perm(perm) + user = self._get_user(user) + perm = self._get_perm(perm) obj = UserToPerm.query()\ .filter(UserToPerm.user == user)\ @@ -587,3 +637,33 @@ class UserModel(BaseModel): .scalar() if obj: self.sa.delete(obj) + + def add_extra_email(self, user, email): + """ + Adds email address to UserEmailMap + + :param user: + :param email: + """ + from rhodecode.model import forms + form = forms.UserExtraEmailForm()() + data = form.to_python(dict(email=email)) + user = self._get_user(user) + + obj = UserEmailMap() + obj.user = user + obj.email = data['email'] + self.sa.add(obj) + return obj + + def delete_extra_email(self, user, email_id): + """ + Removes email address from UserEmailMap + + :param user: + :param email_id: + """ + user = self._get_user(user) + obj = UserEmailMap.query().get(email_id) + if obj: + self.sa.delete(obj) diff --git a/rhodecode/model/users_group.py b/rhodecode/model/users_group.py --- a/rhodecode/model/users_group.py +++ b/rhodecode/model/users_group.py @@ -37,20 +37,18 @@ log = logging.getLogger(__name__) class UsersGroupModel(BaseModel): - def __get_user(self, user): - return self._get_instance(User, user, callback=User.get_by_username) + cls = UsersGroup def __get_users_group(self, users_group): return self._get_instance(UsersGroup, users_group, callback=UsersGroup.get_by_group_name) - def __get_perm(self, permission): - return self._get_instance(Permission, permission, - callback=Permission.get_by_key) - def get(self, users_group_id, cache=False): return UsersGroup.get(users_group_id) + def get_group(self, users_group): + return self.__get_users_group(users_group) + def get_by_name(self, name, cache=False, case_insensitive=False): return UsersGroup.get_by_group_name(name, cache, case_insensitive) @@ -115,7 +113,7 @@ class UsersGroupModel(BaseModel): def add_user_to_group(self, users_group, user): users_group = self.__get_users_group(users_group) - user = self.__get_user(user) + user = self._get_user(user) for m in users_group.members: u = m.user @@ -138,7 +136,7 @@ class UsersGroupModel(BaseModel): def remove_user_from_group(self, users_group, user): users_group = self.__get_users_group(users_group) - user = self.__get_user(user) + user = self._get_user(user) users_group_member = None for m in users_group.members: @@ -160,17 +158,15 @@ class UsersGroupModel(BaseModel): def has_perm(self, users_group, perm): users_group = self.__get_users_group(users_group) - perm = self.__get_perm(perm) + perm = self._get_perm(perm) return UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group == users_group)\ .filter(UsersGroupToPerm.permission == perm).scalar() is not None def grant_perm(self, users_group, perm): - if not isinstance(perm, Permission): - raise Exception('perm needs to be an instance of Permission class') - users_group = self.__get_users_group(users_group) + perm = self._get_perm(perm) # if this permission is already granted skip it _perm = UsersGroupToPerm.query()\ @@ -187,7 +183,7 @@ class UsersGroupModel(BaseModel): def revoke_perm(self, users_group, perm): users_group = self.__get_users_group(users_group) - perm = self.__get_perm(perm) + perm = self._get_perm(perm) obj = UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group == users_group)\ diff --git a/rhodecode/model/validators.py b/rhodecode/model/validators.py new file mode 100644 --- /dev/null +++ b/rhodecode/model/validators.py @@ -0,0 +1,676 @@ +""" +Set of generic validators +""" +import os +import re +import formencode +import logging +from collections import defaultdict +from pylons.i18n.translation import _ +from webhelpers.pylonslib.secure_form import authentication_token + +from formencode.validators import ( + UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, + NotEmpty +) +from rhodecode.lib.compat import OrderedSet +from rhodecode.lib.utils import repo_name_slug +from rhodecode.model.db import RepoGroup, Repository, UsersGroup, User,\ + ChangesetStatus +from rhodecode.lib.exceptions import LdapImportError +from rhodecode.config.routing import ADMIN_PREFIX + +# silence warnings and pylint +UnicodeString, OneOf, Int, Number, Regex, Email, Bool, StringBoolean, Set, \ + NotEmpty + +log = logging.getLogger(__name__) + + +class UniqueList(formencode.FancyValidator): + """ + Unique List ! + """ + messages = dict( + empty=_('Value cannot be an empty list'), + missing_value=_('Value cannot be an empty list'), + ) + + def _to_python(self, value, state): + if isinstance(value, list): + return value + elif isinstance(value, set): + return list(value) + elif isinstance(value, tuple): + return list(value) + elif value is None: + return [] + else: + return [value] + + def empty_value(self, value): + return [] + + +class StateObj(object): + """ + this is needed to translate the messages using _() in validators + """ + _ = staticmethod(_) + + +def M(self, key, state=None, **kwargs): + """ + returns string from self.message based on given key, + passed kw params are used to substitute %(named)s params inside + translated strings + + :param msg: + :param state: + """ + if state is None: + state = StateObj() + else: + state._ = staticmethod(_) + #inject validator into state object + return self.message(key, state, **kwargs) + + +def ValidUsername(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'username_exists': _(u'Username "%(username)s" already exists'), + 'system_invalid_username': + _(u'Username "%(username)s" is forbidden'), + 'invalid_username': + _(u'Username may only contain alphanumeric characters ' + 'underscores, periods or dashes and must begin with ' + 'alphanumeric character') + } + + def validate_python(self, value, state): + if value in ['default', 'new_user']: + msg = M(self, 'system_invalid_username', state, username=value) + raise formencode.Invalid(msg, value, state) + #check if user is unique + old_un = None + if edit: + old_un = User.get(old_data.get('user_id')).username + + if old_un != value or not edit: + if User.get_by_username(value, case_insensitive=True): + msg = M(self, 'username_exists', state, username=value) + raise formencode.Invalid(msg, value, state) + + if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: + msg = M(self, 'invalid_username', state) + raise formencode.Invalid(msg, value, state) + return _validator + + +def ValidRepoUser(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_username': _(u'Username %(username)s is not valid') + } + + def validate_python(self, value, state): + try: + User.query().filter(User.active == True)\ + .filter(User.username == value).one() + except Exception: + msg = M(self, 'invalid_username', state, username=value) + raise formencode.Invalid(msg, value, state, + error_dict=dict(username=msg) + ) + + return _validator + + +def ValidUsersGroup(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_group': _(u'Invalid users group name'), + 'group_exist': _(u'Users group "%(usersgroup)s" already exists'), + 'invalid_usersgroup_name': + _(u'users group name may only contain alphanumeric ' + 'characters underscores, periods or dashes and must begin ' + 'with alphanumeric character') + } + + def validate_python(self, value, state): + if value in ['default']: + msg = M(self, 'invalid_group', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(users_group_name=msg) + ) + #check if group is unique + old_ugname = None + if edit: + old_id = old_data.get('users_group_id') + old_ugname = UsersGroup.get(old_id).users_group_name + + if old_ugname != value or not edit: + is_existing_group = UsersGroup.get_by_group_name(value, + case_insensitive=True) + if is_existing_group: + msg = M(self, 'group_exist', state, usersgroup=value) + raise formencode.Invalid(msg, value, state, + error_dict=dict(users_group_name=msg) + ) + + if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None: + msg = M(self, 'invalid_usersgroup_name', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(users_group_name=msg) + ) + + return _validator + + +def ValidReposGroup(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'group_parent_id': _(u'Cannot assign this group as parent'), + 'group_exists': _(u'Group "%(group_name)s" already exists'), + 'repo_exists': + _(u'Repository with name "%(group_name)s" already exists') + } + + def validate_python(self, value, state): + # TODO WRITE VALIDATIONS + group_name = value.get('group_name') + group_parent_id = value.get('group_parent_id') + + # slugify repo group just in case :) + slug = repo_name_slug(group_name) + + # check for parent of self + parent_of_self = lambda: ( + old_data['group_id'] == int(group_parent_id) + if group_parent_id else False + ) + if edit and parent_of_self(): + msg = M(self, 'group_parent_id', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(group_parent_id=msg) + ) + + old_gname = None + if edit: + old_gname = RepoGroup.get(old_data.get('group_id')).group_name + + if old_gname != group_name or not edit: + + # check group + gr = RepoGroup.query()\ + .filter(RepoGroup.group_name == slug)\ + .filter(RepoGroup.group_parent_id == group_parent_id)\ + .scalar() + + if gr: + msg = M(self, 'group_exists', state, group_name=slug) + raise formencode.Invalid(msg, value, state, + error_dict=dict(group_name=msg) + ) + + # check for same repo + repo = Repository.query()\ + .filter(Repository.repo_name == slug)\ + .scalar() + + if repo: + msg = M(self, 'repo_exists', state, group_name=slug) + raise formencode.Invalid(msg, value, state, + error_dict=dict(group_name=msg) + ) + + return _validator + + +def ValidPassword(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_password': + _(u'Invalid characters (non-ascii) in password') + } + + def validate_python(self, value, state): + try: + (value or '').decode('ascii') + except UnicodeError: + msg = M(self, 'invalid_password', state) + raise formencode.Invalid(msg, value, state,) + return _validator + + +def ValidPasswordsMatch(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'password_mismatch': _(u'Passwords do not match'), + } + + def validate_python(self, value, state): + + pass_val = value.get('password') or value.get('new_password') + if pass_val != value['password_confirmation']: + msg = M(self, 'password_mismatch', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(password_confirmation=msg) + ) + return _validator + + +def ValidAuth(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_password': _(u'invalid password'), + 'invalid_username': _(u'invalid user name'), + 'disabled_account': _(u'Your account is disabled') + } + + def validate_python(self, value, state): + from rhodecode.lib.auth import authenticate + + password = value['password'] + username = value['username'] + + if not authenticate(username, password): + user = User.get_by_username(username) + if user and user.active is False: + log.warning('user %s is disabled' % username) + msg = M(self, 'disabled_account', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(username=msg) + ) + else: + log.warning('user %s failed to authenticate' % username) + msg = M(self, 'invalid_username', state) + msg2 = M(self, 'invalid_password', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(username=msg, password=msg2) + ) + return _validator + + +def ValidAuthToken(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_token': _(u'Token mismatch') + } + + def validate_python(self, value, state): + if value != authentication_token(): + msg = M(self, 'invalid_token', state) + raise formencode.Invalid(msg, value, state) + return _validator + + +def ValidRepoName(edit=False, old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_repo_name': + _(u'Repository name %(repo)s is disallowed'), + 'repository_exists': + _(u'Repository named %(repo)s already exists'), + 'repository_in_group_exists': _(u'Repository "%(repo)s" already ' + 'exists in group "%(group)s"'), + 'same_group_exists': _(u'Repositories group with name "%(repo)s" ' + 'already exists') + } + + def _to_python(self, value, state): + repo_name = repo_name_slug(value.get('repo_name', '')) + repo_group = value.get('repo_group') + if repo_group: + gr = RepoGroup.get(repo_group) + group_path = gr.full_path + group_name = gr.group_name + # value needs to be aware of group name in order to check + # db key This is an actual just the name to store in the + # database + repo_name_full = group_path + RepoGroup.url_sep() + repo_name + else: + group_name = group_path = '' + repo_name_full = repo_name + + value['repo_name'] = repo_name + value['repo_name_full'] = repo_name_full + value['group_path'] = group_path + value['group_name'] = group_name + return value + + def validate_python(self, value, state): + + repo_name = value.get('repo_name') + repo_name_full = value.get('repo_name_full') + group_path = value.get('group_path') + group_name = value.get('group_name') + + if repo_name in [ADMIN_PREFIX, '']: + msg = M(self, 'invalid_repo_name', state, repo=repo_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + + rename = old_data.get('repo_name') != repo_name_full + create = not edit + if rename or create: + + if group_path != '': + if Repository.get_by_repo_name(repo_name_full): + msg = M(self, 'repository_in_group_exists', state, + repo=repo_name, group=group_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + elif RepoGroup.get_by_group_name(repo_name_full): + msg = M(self, 'same_group_exists', state, + repo=repo_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + + elif Repository.get_by_repo_name(repo_name_full): + msg = M(self, 'repository_exists', state, + repo=repo_name) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_name=msg) + ) + return value + return _validator + + +def ValidForkName(*args, **kwargs): + return ValidRepoName(*args, **kwargs) + + +def SlugifyName(): + class _validator(formencode.validators.FancyValidator): + + def _to_python(self, value, state): + return repo_name_slug(value) + + def validate_python(self, value, state): + pass + + return _validator + + +def ValidCloneUri(): + from rhodecode.lib.utils import make_ui + + def url_handler(repo_type, url, ui=None): + if repo_type == 'hg': + from rhodecode.lib.vcs.backends.hg.repository import MercurialRepository + from mercurial.httppeer import httppeer + if url.startswith('http'): + ## initially check if it's at least the proper URL + ## or does it pass basic auth + MercurialRepository._check_url(url) + httppeer(ui, url)._capabilities() + elif url.startswith('svn+http'): + from hgsubversion.svnrepo import svnremoterepo + svnremoterepo(ui, url).capabilities + elif url.startswith('git+http'): + raise NotImplementedError() + + elif repo_type == 'git': + from rhodecode.lib.vcs.backends.git.repository import GitRepository + if url.startswith('http'): + ## initially check if it's at least the proper URL + ## or does it pass basic auth + GitRepository._check_url(url) + elif url.startswith('svn+http'): + raise NotImplementedError() + elif url.startswith('hg+http'): + raise NotImplementedError() + + class _validator(formencode.validators.FancyValidator): + messages = { + 'clone_uri': _(u'invalid clone url'), + 'invalid_clone_uri': _(u'Invalid clone url, provide a ' + 'valid clone http(s)/svn+http(s) url') + } + + def validate_python(self, value, state): + repo_type = value.get('repo_type') + url = value.get('clone_uri') + + if not url: + pass + else: + try: + url_handler(repo_type, url, make_ui('db', clear_session=False)) + except Exception: + log.exception('Url validation failed') + msg = M(self, 'clone_uri') + raise formencode.Invalid(msg, value, state, + error_dict=dict(clone_uri=msg) + ) + return _validator + + +def ValidForkType(old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_fork_type': _(u'Fork have to be the same type as parent') + } + + def validate_python(self, value, state): + if old_data['repo_type'] != value: + msg = M(self, 'invalid_fork_type', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(repo_type=msg) + ) + return _validator + + +def ValidPerms(type_='repo'): + if type_ == 'group': + EMPTY_PERM = 'group.none' + elif type_ == 'repo': + EMPTY_PERM = 'repository.none' + + class _validator(formencode.validators.FancyValidator): + messages = { + 'perm_new_member_name': + _(u'This username or users group name is not valid') + } + + def to_python(self, value, state): + perms_update = OrderedSet() + perms_new = OrderedSet() + # build a list of permission to update and new permission to create + + #CLEAN OUT ORG VALUE FROM NEW MEMBERS, and group them using + new_perms_group = defaultdict(dict) + for k, v in value.copy().iteritems(): + if k.startswith('perm_new_member'): + del value[k] + _type, part = k.split('perm_new_member_') + args = part.split('_') + if len(args) == 1: + new_perms_group[args[0]]['perm'] = v + elif len(args) == 2: + _key, pos = args + new_perms_group[pos][_key] = v + + # fill new permissions in order of how they were added + for k in sorted(map(int, new_perms_group.keys())): + perm_dict = new_perms_group[str(k)] + new_member = perm_dict['name'] + new_perm = perm_dict['perm'] + new_type = perm_dict['type'] + if new_member and new_perm and new_type: + perms_new.add((new_member, new_perm, new_type)) + + for k, v in value.iteritems(): + if k.startswith('u_perm_') or k.startswith('g_perm_'): + member = k[7:] + t = {'u': 'user', + 'g': 'users_group' + }[k[0]] + if member == 'default': + if value.get('private'): + # set none for default when updating to + # private repo + v = EMPTY_PERM + perms_update.add((member, v, t)) + + value['perms_updates'] = list(perms_update) + value['perms_new'] = list(perms_new) + + # update permissions + for k, v, t in perms_new: + try: + if t is 'user': + self.user_db = User.query()\ + .filter(User.active == True)\ + .filter(User.username == k).one() + if t is 'users_group': + self.user_db = UsersGroup.query()\ + .filter(UsersGroup.users_group_active == True)\ + .filter(UsersGroup.users_group_name == k).one() + + except Exception: + log.exception('Updated permission failed') + msg = M(self, 'perm_new_member_type', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(perm_new_member_name=msg) + ) + return value + return _validator + + +def ValidSettings(): + class _validator(formencode.validators.FancyValidator): + def _to_python(self, value, state): + # settings form can't edit user + if 'user' in value: + del value['user'] + return value + + def validate_python(self, value, state): + pass + return _validator + + +def ValidPath(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_path': _(u'This is not a valid path') + } + + def validate_python(self, value, state): + if not os.path.isdir(value): + msg = M(self, 'invalid_path', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(paths_root_path=msg) + ) + return _validator + + +def UniqSystemEmail(old_data={}): + class _validator(formencode.validators.FancyValidator): + messages = { + 'email_taken': _(u'This e-mail address is already taken') + } + + def _to_python(self, value, state): + return value.lower() + + def validate_python(self, value, state): + if (old_data.get('email') or '').lower() != value: + user = User.get_by_email(value, case_insensitive=True) + if user: + msg = M(self, 'email_taken', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(email=msg) + ) + return _validator + + +def ValidSystemEmail(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'non_existing_email': _(u'e-mail "%(email)s" does not exist.') + } + + def _to_python(self, value, state): + return value.lower() + + def validate_python(self, value, state): + user = User.get_by_email(value, case_insensitive=True) + if user is None: + msg = M(self, 'non_existing_email', state, email=value) + raise formencode.Invalid(msg, value, state, + error_dict=dict(email=msg) + ) + + return _validator + + +def LdapLibValidator(): + class _validator(formencode.validators.FancyValidator): + messages = { + + } + + def validate_python(self, value, state): + try: + import ldap + ldap # pyflakes silence ! + except ImportError: + raise LdapImportError() + + return _validator + + +def AttrLoginValidator(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'invalid_cn': + _(u'The LDAP Login attribute of the CN must be specified - ' + 'this is the name of the attribute that is equivalent ' + 'to "username"') + } + + def validate_python(self, value, state): + if not value or not isinstance(value, (str, unicode)): + msg = M(self, 'invalid_cn', state) + raise formencode.Invalid(msg, value, state, + error_dict=dict(ldap_attr_login=msg) + ) + + return _validator + + +def NotReviewedRevisions(): + class _validator(formencode.validators.FancyValidator): + messages = { + 'rev_already_reviewed': + _(u'Revisions %(revs)s are already part of pull request ' + 'or have set status') + } + + def validate_python(self, value, state): + # check revisions if they are not reviewed, or a part of another + # pull request + statuses = ChangesetStatus.query()\ + .filter(ChangesetStatus.revision.in_(value)).all() + errors = [] + for cs in statuses: + if cs.pull_request_id: + errors.append(['pull_req', cs.revision[:12]]) + elif cs.status: + errors.append(['status', cs.revision[:12]]) + + if errors: + revs = ','.join([x[1] for x in errors]) + msg = M(self, 'rev_already_reviewed', state, revs=revs) + raise formencode.Invalid(msg, value, state, + error_dict=dict(revisions=revs) + ) + + return _validator diff --git a/rhodecode/public/css/codemirror.css b/rhodecode/public/css/codemirror.css --- a/rhodecode/public/css/codemirror.css +++ b/rhodecode/public/css/codemirror.css @@ -1,14 +1,57 @@ .CodeMirror { - overflow: auto; - height: 450px; line-height: 1em; font-family: monospace; - _position: relative; /* IE6 hack */ - margin:20px; + + /* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */ + position: relative; + /* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */ + overflow: hidden; +} + +.CodeMirror-scroll { + overflow-x: auto; + overflow-y: hidden; + height: 300px; + /* This is needed to prevent an IE[67] bug where the scrolled content + is visible outside of the scrolling box. */ + position: relative; + outline: none; +} + +/* Vertical scrollbar */ +.CodeMirror-scrollbar { + float: right; + overflow-x: hidden; + overflow-y: scroll; + + /* This corrects for the 1px gap introduced to the left of the scrollbar + by the rule for .CodeMirror-scrollbar-inner. */ + margin-left: -1px; +} +.CodeMirror-scrollbar-inner { + /* This needs to have a nonzero width in order for the scrollbar to appear + in Firefox and IE9. */ + width: 1px; +} +.CodeMirror-scrollbar.cm-sb-overlap { + /* Ensure that the scrollbar appears in Lion, and that it overlaps the content + rather than sitting to the right of it. */ + position: absolute; + z-index: 1; + float: none; + right: 0; + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-nonoverlap { + min-width: 12px; +} +.CodeMirror-scrollbar.cm-sb-ie7 { + min-width: 18px; } .CodeMirror-gutter { position: absolute; left: 0; top: 0; + z-index: 10; background-color: #f7f7f7; border-right: 1px solid #eee; min-width: 2em; @@ -18,9 +61,16 @@ color: #aaa; text-align: right; padding: .4em .2em .4em .4em; + white-space: pre !important; } .CodeMirror-lines { padding: .4em; + white-space: pre; + cursor: text; +} +.CodeMirror-lines * { + /* Necessary for throw-scrolling to decelerate properly on Safari. */ + pointer-events: none; } .CodeMirror pre { @@ -30,26 +80,89 @@ border-radius: 0; border-width: 0; margin: 0; padding: 0; background: transparent; font-family: inherit; + font-size: inherit; + padding: 0; margin: 0; + white-space: pre; + word-wrap: normal; + line-height: inherit; + color: inherit; } -.CodeMirror-cursor { +.CodeMirror-wrap pre { + word-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} +.CodeMirror-wrap .CodeMirror-scroll { + overflow-x: hidden; +} + +.CodeMirror textarea { + outline: none !important; +} + +.CodeMirror pre.CodeMirror-cursor { z-index: 10; position: absolute; visibility: hidden; - border-left: 1px solid black !important; + border-left: 1px solid black; + border-right: none; + width: 0; } -.CodeMirror-focused .CodeMirror-cursor { +.cm-keymap-fat-cursor pre.CodeMirror-cursor { + width: auto; + border: 0; + background: transparent; + background: rgba(0, 200, 0, .4); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800); +} +/* Kludge to turn off filter in ie9+, which also accepts rgba */ +.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) { + filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); +} +.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {} +.CodeMirror-focused pre.CodeMirror-cursor { visibility: visible; } -span.CodeMirror-selected { - background: #ccc !important; - color: HighlightText !important; -} -.CodeMirror-focused span.CodeMirror-selected { - background: Highlight !important; +div.CodeMirror-selected { background: #d9d9d9; } +.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; } + +.CodeMirror-searching { + background: #ffa; + background: rgba(255, 255, 0, .4); } -.CodeMirror-matchingbracket {color: #0f0 !important;} -.CodeMirror-nonmatchingbracket {color: #f22 !important;} -.CodeMirror-gutter-text{color: #003367 !important;} \ No newline at end of file +/* Default theme */ + +.cm-s-default span.cm-keyword {color: #708;} +.cm-s-default span.cm-atom {color: #219;} +.cm-s-default span.cm-number {color: #164;} +.cm-s-default span.cm-def {color: #00f;} +.cm-s-default span.cm-variable {color: black;} +.cm-s-default span.cm-variable-2 {color: #05a;} +.cm-s-default span.cm-variable-3 {color: #085;} +.cm-s-default span.cm-property {color: black;} +.cm-s-default span.cm-operator {color: black;} +.cm-s-default span.cm-comment {color: #a50;} +.cm-s-default span.cm-string {color: #a11;} +.cm-s-default span.cm-string-2 {color: #f50;} +.cm-s-default span.cm-meta {color: #555;} +.cm-s-default span.cm-error {color: #f00;} +.cm-s-default span.cm-qualifier {color: #555;} +.cm-s-default span.cm-builtin {color: #30a;} +.cm-s-default span.cm-bracket {color: #cc7;} +.cm-s-default span.cm-tag {color: #170;} +.cm-s-default span.cm-attribute {color: #00c;} +.cm-s-default span.cm-header {color: blue;} +.cm-s-default span.cm-quote {color: #090;} +.cm-s-default span.cm-hr {color: #999;} +.cm-s-default span.cm-link {color: #00c;} + +span.cm-header, span.cm-strong {font-weight: bold;} +span.cm-em {font-style: italic;} +span.cm-emstrong {font-style: italic; font-weight: bold;} +span.cm-link {text-decoration: underline;} + +div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} +div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} diff --git a/rhodecode/public/css/style.css b/rhodecode/public/css/style.css --- a/rhodecode/public/css/style.css +++ b/rhodecode/public/css/style.css @@ -220,6 +220,28 @@ div.options a { margin-top: 5px; } +.empty_data{ + color:#B9B9B9; +} + +a.permalink{ + visibility: hidden; +} + +a.permalink:hover{ + text-decoration: none; +} + +h1:hover > a.permalink, +h2:hover > a.permalink, +h3:hover > a.permalink, +h4:hover > a.permalink, +h5:hover > a.permalink, +h6:hover > a.permalink, +div:hover > a.permalink { + visibility: visible; +} + #header { margin: 0; padding: 0 10px; @@ -232,7 +254,7 @@ div.options a { -moz-border-radius: 0px 0px 8px 8px; border-radius: 0px 0px 8px 8px; height: 37px; - background-color: #003B76; + background-color: #003B76; background-repeat: repeat-x; background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) ); background-image: -moz-linear-gradient(top, #003b76, #00376e); @@ -330,6 +352,11 @@ div.options a { z-index: auto !important; } +.header-pos-fix{ + margin-top: -44px; + padding-top: 44px; +} + #header #header-inner #home a { height: 40px; width: 46px; @@ -631,6 +658,15 @@ div.options a { padding: 12px 9px 7px 24px; } +#header #header-inner #quick li ul li a.pull_request,#header #header-inner #quick li ul li a.pull_request:hover + { + background: #FFF url("../images/icons/arrow_join.png") no-repeat 4px + 9px; + width: 167px; + margin: 0; + padding: 12px 9px 7px 24px; +} + #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover { background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px; @@ -1065,6 +1101,10 @@ tbody .yui-dt-editable { cursor: pointer color: #FFFFFF; } +#content div.box div.title .link-white.current{ + color: #BFE3FF; +} + #content div.box div.title ul.links li { list-style: none; float: left; @@ -1398,7 +1438,8 @@ tbody .yui-dt-editable { cursor: pointer margin: 0 0 0 0px; } -#content div.box div.form div.fields div.field div.input input { +#content div.box div.form div.fields div.field div.input input, +.reviewer_ac input { background: #FFF; border-top: 1px solid #b3b3b3; border-left: 1px solid #b3b3b3; @@ -1518,12 +1559,21 @@ input.disabled { padding: 5px 5px 5px 0; } -#content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus +#content div.box div.form div.fields div.field input[type=text]:focus, +#content div.box div.form div.fields div.field input[type=password]:focus, +#content div.box div.form div.fields div.field input[type=file]:focus, +#content div.box div.form div.fields div.field textarea:focus, +#content div.box div.form div.fields div.field select:focus, +.reviewer_ac input:focus { background: #f6f6f6; border-color: #666; } +.reviewer_ac { + padding:10px +} + div.form div.fields div.field div.button { margin: 0; padding: 0 0 0 8px; @@ -1689,7 +1739,12 @@ div.form div.fields div.field div.button float: right; } -#content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot +#content div.box div.pagination-wh a, +#content div.box div.pagination-wh span.pager_dotdot, +#content div.box div.pagination-wh span.yui-pg-previous, +#content div.box div.pagination-wh span.yui-pg-last, +#content div.box div.pagination-wh span.yui-pg-next, +#content div.box div.pagination-wh span.yui-pg-first { height: 1%; float: left; @@ -1777,6 +1832,81 @@ div.form div.fields div.field div.button } +#summary .metatag { + display: inline-block; + padding: 3px 5px; + margin-bottom: 3px; + margin-right: 1px; + border-radius: 5px; +} + +#content div.box #summary p { + margin-bottom: -5px; + width: 600px; + white-space: pre-wrap; +} + +#content div.box #summary p:last-child { + margin-bottom: 9px; +} + +#content div.box #summary p:first-of-type { + margin-top: 9px; +} + + .metatag { + display: inline-block; + margin-right: 1px; + -webkit-border-radius: 4px 4px 4px 4px; + -khtml-border-radius: 4px 4px 4px 4px; + -moz-border-radius: 4px 4px 4px 4px; + border-radius: 4px 4px 4px 4px; + + border: solid 1px #9CF; + padding: 2px 3px 2px 3px !important; + background-color: #DEF; +} + +.metatag[tag="dead"] { + background-color: #E44; +} + +.metatag[tag="stale"] { + background-color: #EA4; +} + +.metatag[tag="featured"] { + background-color: #AEA; +} + +.metatag[tag="requires"] { + background-color: #9CF; +} + +.metatag[tag="recommends"] { + background-color: #BDF; +} + +.metatag[tag="lang"] { + background-color: #FAF474; +} + +.metatag[tag="license"] { + border: solid 1px #9CF; + background-color: #DEF; + target-new: tab !important; +} +.metatag[tag="see"] { + border: solid 1px #CBD; + background-color: #EDF; +} + +a.metatag[tag="license"]:hover { + background-color: #003367; + color: #FFF; + text-decoration: none; +} + #summary .desc { white-space: pre; width: 100%; @@ -1923,7 +2053,7 @@ div.form div.fields div.field div.button padding: 4px; position: absolute; width: 278px; - + background-color: #003B76; background-repeat: repeat-x; background-image: -khtml-gradient(linear, left top, left bottom, from(#003B76), to(#00376E) ); background-image: -moz-linear-gradient(top, #003b76, #00376e); @@ -2247,6 +2377,20 @@ h3.files_location { padding: 5px !important; } +.file_history{ + padding-top:10px; + font-size:16px; +} +.file_author{ + float: left; +} + +.file_author .item{ + float:left; + padding:5px; + color: #888; +} + .tablerow0 { background-color: #F8F8F8; } @@ -2333,7 +2477,7 @@ h3.files_location { padding: 2px 0px 2px 0px; } -.cs_files .cs_added { +.cs_files .cs_added,.cs_files .cs_A { background: url("../images/icons/page_white_add.png") no-repeat scroll 3px; height: 16px; @@ -2342,7 +2486,7 @@ h3.files_location { text-align: left; } -.cs_files .cs_changed { +.cs_files .cs_changed,.cs_files .cs_M { background: url("../images/icons/page_white_edit.png") no-repeat scroll 3px; height: 16px; @@ -2351,7 +2495,7 @@ h3.files_location { text-align: left; } -.cs_files .cs_removed { +.cs_files .cs_removed,.cs_files .cs_D { background: url("../images/icons/page_white_delete.png") no-repeat scroll 3px; height: 16px; @@ -2447,6 +2591,31 @@ h3.files_location { font-weight: bold !important; } +.changeset-status-container{ + padding-right: 5px; + margin-top:1px; + float:right; + height:14px; +} +.code-header .changeset-status-container{ + float:left; + padding:2px 0px 0px 2px; +} +.changeset-status-container .changeset-status-lbl{ + color: rgb(136, 136, 136); + float: left; + padding: 3px 4px 0px 0px +} +.code-header .changeset-status-container .changeset-status-lbl{ + float: left; + padding: 0px 4px 0px 0px; +} +.changeset-status-container .changeset-status-ico{ + float: left; +} +.code-header .changeset-status-container .changeset-status-ico, .container .changeset-status-ico{ + float: left; +} .right .comments-container{ padding-right: 5px; margin-top:1px; @@ -2815,6 +2984,13 @@ table.code-browser .submodule-dir { box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6); } +.mentions-container{ + width: 90% !important; +} +.mentions-container .yui-ac-content{ + width: 100% !important; +} + .ac { vertical-align: top; } @@ -2925,6 +3101,13 @@ table.code-browser .submodule-dir { text-align: left; } +.accept_icon { + background: url("../images/icons/accept.png") no-repeat scroll 3px; + padding-left: 20px; + padding-top: 0px; + text-align: left; +} + .edit_icon { background: url("../images/icons/folder_edit.png") no-repeat scroll 3px; padding-left: 20px; @@ -3042,6 +3225,10 @@ table.code-browser .submodule-dir { } +.error_red { + color:red; +} + .error_msg { background-color: #c43c35; background-repeat: repeat-x; @@ -3228,6 +3415,11 @@ div.gravatar img { .ui-btn.xsmall{ padding: 1px 2px 1px 1px; } + +.ui-btn.large{ + padding: 6px 12px; +} + .ui-btn.clone{ padding: 5px 2px 6px 1px; margin: 0px -4px 3px 0px; @@ -3268,6 +3460,7 @@ div.gravatar img { .ui-btn.blue{ + color:#fff; background-color: #339bb9; background-repeat: repeat-x; background-image: -khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9)); @@ -3297,6 +3490,10 @@ div.gravatar img { border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); } +.ui-btn.active{ + font-weight: bold; +} + ins,div.options a:hover { text-decoration: none; } @@ -3452,12 +3649,14 @@ div#legend_data,div#legend_container,div margin: 2px 0 0 4px; } -div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input - { - color: #000; - font-size: 11px; - font-weight: 700; - margin: 0; +div.form div.fields div.field div.button input, +#content div.box div.form div.fields div.buttons input +div.form div.fields div.buttons input, +#content div.box div.action div.button input { + /*color: #000;*/ + font-size: 11px; + font-weight: 700; + margin: 0; } input.ui-button { @@ -3687,6 +3886,26 @@ div#legend_container table td,div#legend padding:0px 0px 0px 10px; } +.reviewers_member{ + height: 15px; + padding:0px 0px 0px 10px; +} + +.emails_wrap{ + padding: 0px 20px; +} + +.emails_wrap .email_entry{ + height: 30px; + padding:0px 0px 0px 10px; +} +.emails_wrap .email_entry .email{ + float: left +} +.emails_wrap .email_entry .email_action{ + float: left +} + /*README STYLE*/ div.readme { @@ -3891,6 +4110,7 @@ div.rst-block pre { background: #f8f8f8; padding: 4px; border-bottom: 1px solid #ddd; + height: 18px; } .comments .comment .meta img { @@ -3899,9 +4119,13 @@ div.rst-block pre { .comments .comment .meta .user { font-weight: bold; + float: left; + padding: 4px 2px 2px 2px; } .comments .comment .meta .date { + float: left; + padding:4px 4px 0px 4px; } .comments .comment .text { @@ -3920,6 +4144,11 @@ div.rst-block pre { /** comment form **/ +.status-block{ + height:80px; + clear:both +} + .comment-form .clearfix{ background: #EEE; -webkit-border-radius: 4px; @@ -4089,6 +4318,7 @@ form.comment-inline-form { background: #f8f8f8; padding: 4px; border-bottom: 1px solid #ddd; + height: 20px; } .inline-comments .comment .meta img { @@ -4097,9 +4327,13 @@ form.comment-inline-form { .inline-comments .comment .meta .user { font-weight: bold; + float:left; + padding: 3px; } .inline-comments .comment .meta .date { + float:left; + padding: 3px; } .inline-comments .comment .text { @@ -4161,7 +4395,7 @@ form.comment-inline-form { background: none repeat scroll 0 0 transparent; padding: 0px 0px 0px 8px; } -.notification-header .desc.unread{ +.notification-list .container .notification-header .desc{ font-weight: bold; font-size: 17px; } @@ -4178,6 +4412,11 @@ form.comment-inline-form { padding-top: 8px; cursor: pointer; } +.notification-header .read-notifications{ + float: right; + padding-top: 8px; + cursor: pointer; +} .notification-subject{ clear:both; border-bottom: 1px solid #eee; @@ -4190,6 +4429,15 @@ form.comment-inline-form { } /**** +PULL REQUESTS +*****/ +.pullrequests_section_head { + padding:10px 10px 10px 0px; + font-size:16px; + font-weight: bold; +} + +/**** PERMS *****/ #perms .perms_section_head { diff --git a/rhodecode/public/images/arrow_right_64.png b/rhodecode/public/images/arrow_right_64.png new file mode 100644 index 0000000000000000000000000000000000000000..93ee3eb3532f021b34b89b020419724edc90a125 GIT binary patch literal 3105 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 diff --git a/rhodecode/public/images/icons/flag_status_rejected.png b/rhodecode/public/images/icons/flag_status_rejected.png new file mode 100644 index 0000000000000000000000000000000000000000..e8a602da7b17a323b2c9afe3d8aac62cb717a0b8 GIT binary patch literal 665 zc$@*50%rY*P)W2MxECPn{*ELdZ1op0xyVpxzDdhz3h^YVPo^S(T97!Wp}{CC5s zOO0&MCbYN{*S_u5sKy(UFm7-Lr)M|odnZ1F`|tp z;QLC~;7ZS9V!0QqC_yL9nVW1CJTMZN4M(R$+ zOTZBsFr_4h`&3JNv0I_O8fwI~`z}0v3|$@I8UFpf@&;uE8s7DoF&~bRfJhmzX*a6< zs}(O~Kx~cpZxH}pS*S{qZRPWYW?G`zW8AQcn3#1@S*BcNV6f!zir{5lPjYZSt>eR~E27PJ0+es0y~s0ch&nN;M*NkCc% zXiQ#beHkC&om4IpK8qPut?)aNVjs<%39&#|d3=N1!OZi|I!ONj#Vr@M%?lVEC`+Fg zAQwL{?FfzVoB-$9WC=Ju7r^r86-w*!nR~wgLM67#gs;7-00000NkvXXu0mjfgI^=` diff --git a/rhodecode/public/images/icons/flag_status_under_review.png b/rhodecode/public/images/icons/flag_status_under_review.png new file mode 100644 index 0000000000000000000000000000000000000000..14c89a5430dd9e34b445564c972634474d0aebf9 GIT binary patch literal 671 zc$@*B0$}}#P)h~F3KnqFOq_yZYrcmh=dA~yr_$e+EQlLNOGM%Ml;i# zU7Vw6Zfq9mSh}^jHcU1p^JCLZD)&8iwzFjZJLh0y1$p5v{=642&yVMMd0qklkxl-4 z(M8=Y*1b6uGPP|>4YuT`RLb%n4=(L%zJGQLxM;W>UlFiB6XECJF0cUu7*7+ty}2;F z$iZSFgb#K51w;U7TAHJ!%Yznj4XknkpGg9?Cj=tncaV(Pu*kZQi0HutwHVN~Vz#dy z?bmnnK*b+mb6>}?_`=jZ#9R*%o32OVJB?(ZAJO?1uu&s|o*D$)H=w90g!f}38ZYGW zz$O8p=(;nXx{H4CfX`SUBs_Xqz?h(#i-rRvaF3io=dD6abXW1kNIxP?iIpSj?Wx$b z3_jBy_|1Dc;0l--67ewKMJxQCo0xJ+mkbSu;p!Ft4Rlf0oS|{aQrJ-ezvUp8Lx_x3 zf|*qy86QQ=Pa*1S#uu{;V{K(HlSg^LnFh2}S$JV`x&48y2sa2SQ(^A?I@lE=Sas)EyF>d_)lVCHYIEUy zbsEa6J9wZZ4XE4CdrjneSRacpBp1O#uEo2il0@cCa0R6=D|ZSVWX`|hBK-r%dTNTZ z0=`-mC$j>CVtvDr^a`&E=)8S^2dY;CJYW8W{JA0X?l;dl(LPG4VetR}002ovPDHLk FV1l@oDii' + // Wraps and hides input textarea - '' + - '
' + + '
' + // The vertical scrollbar. Horizontal scrolling is handled by the scroller itself. + '
' + // The empty scrollbar content, used solely for managing the scrollbar thumb. + '
' + // This must be before the scroll area because it's float-right. + '
' + '
' + // Set to the height of the text, causes scrolling - '
' + '
' + // Moved around its parent to cover visible view '
' + // Provides positioning relative to (visible) text origin - '
' + + '
' + + // Used to measure text size + '
' + '
 
' + // Absolutely positioned blinky cursor - '
' + // This DIV contains the actual code + '' + // Used to force a width + '
' + // DIVs containing the selection and the actual code '
'; if (place.appendChild) place.appendChild(wrapper); else place(wrapper); // I've never seen more elegant code in my life. var inputDiv = wrapper.firstChild, input = inputDiv.firstChild, scroller = wrapper.lastChild, code = scroller.firstChild, - measure = code.firstChild, mover = measure.nextSibling, - gutter = mover.firstChild, gutterText = gutter.firstChild, - lineSpace = gutter.nextSibling.firstChild, - cursor = lineSpace.firstChild, lineDiv = cursor.nextSibling; - if (options.tabindex != null) input.tabindex = options.tabindex; + mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild, + lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild, + cursor = measure.nextSibling, widthForcer = cursor.nextSibling, + selectionDiv = widthForcer.nextSibling, lineDiv = selectionDiv.nextSibling, + scrollbar = inputDiv.nextSibling, scrollbarInner = scrollbar.firstChild; + themeChanged(); keyMapChanged(); + // Needed to hide big blue blinking cursor on Mobile Safari + if (ios) input.style.width = "0px"; + if (!webkit) scroller.draggable = true; + lineSpace.style.outline = "none"; + if (options.tabindex != null) input.tabIndex = options.tabindex; + if (options.autofocus) focusInput(); if (!options.gutter && !options.lineNumbers) gutter.style.display = "none"; + // Needed to handle Tab key in KHTML + if (khtml) inputDiv.style.height = "1px", inputDiv.style.position = "absolute"; + + // Check for OS X >= 10.7. If so, we need to force a width on the scrollbar, and + // make it overlap the content. (But we only do this if the scrollbar doesn't already + // have a natural width. If the mouse is plugged in or the user sets the system pref + // to always show scrollbars, the scrollbar shouldn't overlap.) + if (mac_geLion) { + scrollbar.className += (overlapScrollbars() ? " cm-sb-overlap" : " cm-sb-nonoverlap"); + } else if (ie_lt8) { + // Need to set a minimum width to see the scrollbar on IE7 (but must not set it on IE8). + scrollbar.className += " cm-sb-ie7"; + } // Check for problem with IE innerHTML not working when we have a // P (or similar) parent node. try { stringWidth("x"); } catch (e) { - if (e.message.match(/unknown runtime/i)) + if (e.message.match(/runtime/i)) e = new Error("A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)"); throw e; } @@ -55,34 +79,32 @@ var CodeMirror = (function() { // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval. var poll = new Delayed(), highlight = new Delayed(), blinker; - // mode holds a mode API object. lines an array of Line objects - // (see Line constructor), work an array of lines that should be - // parsed, and history the undo history (instance of History - // constructor). - var mode, lines = [new Line("")], work, focused; + // mode holds a mode API object. doc is the tree of Line objects, + // work an array of lines that should be parsed, and history the + // undo history (instance of History constructor). + var mode, doc = new BranchChunk([new LeafChunk([new Line("")])]), work, focused; loadMode(); // The selection. These are always maintained to point at valid // positions. Inverted is used to remember that the user is // selecting bottom-to-top. var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false}; // Selection-related flags. shiftSelecting obviously tracks - // whether the user is holding shift. reducedSelection is a hack - // to get around the fact that we can't create inverted - // selections. See below. - var shiftSelecting, reducedSelection, lastClick, lastDoubleClick, draggingText; + // whether the user is holding shift. + var shiftSelecting, lastClick, lastDoubleClick, lastScrollTop = 0, lastScrollLeft = 0, draggingText, + overwrite = false, suppressEdits = false; // Variables used by startOperation/endOperation to track what // happened during the operation. - var updateInput, changes, textChanged, selectionChanged, leaveInputAlone, gutterDirty; + var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone, + gutterDirty, callbacks; // Current visible range (may be bigger than the view window). - var showingFrom = 0, showingTo = 0, lastHeight = 0, curKeyId = null; - // editing will hold an object describing the things we put in the - // textarea, to help figure out whether something changed. - // bracketHighlighted is used to remember that a backet has been + var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0; + // bracketHighlighted is used to remember that a bracket has been // marked. - var editing, bracketHighlighted; + var bracketHighlighted; // Tracks the maximum line length so that the horizontal scrollbar // can be kept static when scrolling. - var maxLine = "", maxWidth; + var maxLine = "", updateMaxLine = false, maxLineChanged = true; + var tabCache = {}; // Initialize the content. operation(function(){setValue(options.value || ""); updateInput = false;})(); @@ -91,38 +113,53 @@ var CodeMirror = (function() { // Register our event handlers. connect(scroller, "mousedown", operation(onMouseDown)); connect(scroller, "dblclick", operation(onDoubleClick)); - connect(lineSpace, "dragstart", onDragStart); + connect(lineSpace, "selectstart", e_preventDefault); // Gecko browsers fire contextmenu *after* opening the menu, at // which point we can't mess with it anymore. Context menu is // handled in onMouseDown for Gecko. if (!gecko) connect(scroller, "contextmenu", onContextMenu); - connect(scroller, "scroll", function() { - updateDisplay([]); - if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; - if (options.onScroll) options.onScroll(instance); - }); + connect(scroller, "scroll", onScroll); + connect(scrollbar, "scroll", onScroll); + connect(scrollbar, "mousedown", function() {setTimeout(focusInput, 0);}); + connect(scroller, "mousewheel", onMouseWheel); + connect(scroller, "DOMMouseScroll", onMouseWheel); connect(window, "resize", function() {updateDisplay(true);}); connect(input, "keyup", operation(onKeyUp)); - connect(input, "input", function() {fastPoll(curKeyId);}); + connect(input, "input", fastPoll); connect(input, "keydown", operation(onKeyDown)); connect(input, "keypress", operation(onKeyPress)); connect(input, "focus", onFocus); connect(input, "blur", onBlur); - connect(scroller, "dragenter", e_stop); - connect(scroller, "dragover", e_stop); - connect(scroller, "drop", operation(onDrop)); + if (options.dragDrop) { + connect(scroller, "dragstart", onDragStart); + function drag_(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; + e_stop(e); + } + connect(scroller, "dragenter", drag_); + connect(scroller, "dragover", drag_); + connect(scroller, "drop", operation(onDrop)); + } connect(scroller, "paste", function(){focusInput(); fastPoll();}); - connect(input, "paste", function(){fastPoll();}); - connect(input, "cut", function(){fastPoll();}); + connect(input, "paste", fastPoll); + connect(input, "cut", operation(function(){ + if (!options.readOnly) replaceSelection(""); + })); + + // Needed to handle Tab key in KHTML + if (khtml) connect(code, "mouseup", function() { + if (document.activeElement == input) input.blur(); + focusInput(); + }); // IE throws unspecified error in certain cases, when // trying to access activeElement before onload - var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { } - if (hasFocus) setTimeout(onFocus, 20); + var hasFocus; try { hasFocus = (document.activeElement == input); } catch(e) { } + if (hasFocus || options.autofocus) setTimeout(onFocus, 20); else onBlur(); - function isLine(l) {return l >= 0 && l < lines.length;} + function isLine(l) {return l >= 0 && l < doc.size;} // The instance object that we'll return. Mostly calls out to // local functions in the CodeMirror function. Some do some extra // range checking and/or clipping. operation is used to wrap the @@ -133,47 +170,74 @@ var CodeMirror = (function() { setValue: operation(setValue), getSelection: getSelection, replaceSelection: operation(replaceSelection), - focus: function(){focusInput(); onFocus(); fastPoll();}, + focus: function(){window.focus(); focusInput(); onFocus(); fastPoll();}, setOption: function(option, value) { + var oldVal = options[option]; options[option] = value; - if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber") - operation(gutterChanged)(); - else if (option == "mode" || option == "indentUnit") loadMode(); - else if (option == "readOnly" && value == "nocursor") input.blur(); - else if (option == "theme") scroller.className = scroller.className.replace(/cm-s-\w+/, "cm-s-" + value); + if (option == "mode" || option == "indentUnit") loadMode(); + else if (option == "readOnly" && value == "nocursor") {onBlur(); input.blur();} + else if (option == "readOnly" && !value) {resetInput(true);} + else if (option == "theme") themeChanged(); + else if (option == "lineWrapping" && oldVal != value) operation(wrappingChanged)(); + else if (option == "tabSize") updateDisplay(true); + else if (option == "keyMap") keyMapChanged(); + if (option == "lineNumbers" || option == "gutter" || option == "firstLineNumber" || option == "theme") { + gutterChanged(); + updateDisplay(true); + } }, getOption: function(option) {return options[option];}, undo: operation(undo), redo: operation(redo), indentLine: operation(function(n, dir) { - if (isLine(n)) indentLine(n, dir == null ? "smart" : dir ? "add" : "subtract"); + if (typeof dir != "string") { + if (dir == null) dir = options.smartIndent ? "smart" : "prev"; + else dir = dir ? "add" : "subtract"; + } + if (isLine(n)) indentLine(n, dir); }), + indentSelection: operation(indentSelected), historySize: function() {return {undo: history.done.length, redo: history.undone.length};}, clearHistory: function() {history = new History();}, matchBrackets: operation(function(){matchBrackets(true);}), - getTokenAt: function(pos) { + getTokenAt: operation(function(pos) { pos = clipPos(pos); - return lines[pos.line].getTokenAt(mode, getStateBefore(pos.line), pos.ch); - }, + return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch); + }), getStateAfter: function(line) { - line = clipLine(line == null ? lines.length - 1: line); + line = clipLine(line == null ? doc.size - 1: line); return getStateBefore(line + 1); }, - cursorCoords: function(start){ + cursorCoords: function(start, mode) { if (start == null) start = sel.inverted; - return pageCoords(start ? sel.from : sel.to); + return this.charCoords(start ? sel.from : sel.to, mode); }, - charCoords: function(pos){return pageCoords(clipPos(pos));}, + charCoords: function(pos, mode) { + pos = clipPos(pos); + if (mode == "local") return localCoords(pos, false); + if (mode == "div") return localCoords(pos, true); + return pageCoords(pos); + }, coordsChar: function(coords) { var off = eltOffset(lineSpace); - var line = clipLine(Math.min(lines.length - 1, showingFrom + Math.floor((coords.y - off.top) / lineHeight()))); - return clipPos({line: line, ch: charFromX(clipLine(line), coords.x - off.left)}); + return coordsChar(coords.x - off.left, coords.y - off.top); }, - getSearchCursor: function(query, pos, caseFold) {return new SearchCursor(query, pos, caseFold);}, markText: operation(markText), + setBookmark: setBookmark, + findMarksAt: findMarksAt, setMarker: operation(addGutterMarker), clearMarker: operation(removeGutterMarker), setLineClass: operation(setLineClass), + hideLine: operation(function(h) {return setLineHidden(h, true);}), + showLine: operation(function(h) {return setLineHidden(h, false);}), + onDeleteLine: function(line, f) { + if (typeof line == "number") { + if (!isLine(line)) return null; + line = getLine(line); + } + (line.handlers || (line.handlers = [])).push(f); + return line; + }, lineInfo: lineInfo, addWidget: function(pos, node, scroll, vert, horiz) { pos = localCoords(clipPos(pos)); @@ -182,7 +246,7 @@ var CodeMirror = (function() { code.appendChild(node); if (vert == "over") top = pos.y; else if (vert == "near") { - var vspace = Math.max(scroller.offsetHeight, lines.length * lineHeight()), + var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()), hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft(); if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight) top = pos.y - node.offsetHeight; @@ -203,20 +267,24 @@ var CodeMirror = (function() { scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight); }, - lineCount: function() {return lines.length;}, + lineCount: function() {return doc.size;}, + clipPos: clipPos, getCursor: function(start) { if (start == null) start = sel.inverted; return copyPos(start ? sel.from : sel.to); }, somethingSelected: function() {return !posEq(sel.from, sel.to);}, - setCursor: operation(function(line, ch) { - if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch); - else setCursor(line, ch); + setCursor: operation(function(line, ch, user) { + if (ch == null && typeof line.line == "number") setCursor(line.line, line.ch, user); + else setCursor(line, ch, user); }), - setSelection: operation(function(from, to) {setSelection(clipPos(from), clipPos(to || from));}), - getLine: function(line) {if (isLine(line)) return lines[line].text;}, + setSelection: operation(function(from, to, user) { + (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from)); + }), + getLine: function(line) {if (isLine(line)) return getLine(line).text;}, + getLineHandle: function(line) {if (isLine(line)) return getLine(line);}, setLine: operation(function(line, text) { - if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: lines[line].text.length}); + if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length}); }), removeLine: operation(function(line) { if (isLine(line)) replaceRange("", {line: line, ch: 0}, clipPos({line: line+1, ch: 0})); @@ -224,44 +292,99 @@ var CodeMirror = (function() { replaceRange: operation(replaceRange), getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));}, - coordsFromIndex: function(index) { - var total = lines.length, pos = 0, line, ch, len; - - for (line = 0; line < total; line++) { - len = lines[line].text.length + 1; - if (pos + len > index) { ch = index - pos; break; } - pos += len; + triggerOnKeyDown: operation(onKeyDown), + execCommand: function(cmd) {return commands[cmd](instance);}, + // Stuff used by commands, probably not much use to outside code. + moveH: operation(moveH), + deleteH: operation(deleteH), + moveV: operation(moveV), + toggleOverwrite: function() { + if(overwrite){ + overwrite = false; + cursor.className = cursor.className.replace(" CodeMirror-overwrite", ""); + } else { + overwrite = true; + cursor.className += " CodeMirror-overwrite"; } - return clipPos({line: line, ch: ch}); + }, + + posFromIndex: function(off) { + var lineNo = 0, ch; + doc.iter(0, doc.size, function(line) { + var sz = line.text.length + 1; + if (sz > off) { ch = off; return true; } + off -= sz; + ++lineNo; + }); + return clipPos({line: lineNo, ch: ch}); + }, + indexFromPos: function (coords) { + if (coords.line < 0 || coords.ch < 0) return 0; + var index = coords.ch; + doc.iter(0, coords.line, function (line) { + index += line.text.length + 1; + }); + return index; + }, + scrollTo: function(x, y) { + if (x != null) scroller.scrollLeft = x; + if (y != null) scrollbar.scrollTop = y; + updateDisplay([]); + }, + getScrollInfo: function() { + return {x: scroller.scrollLeft, y: scrollbar.scrollTop, + height: scrollbar.scrollHeight, width: scroller.scrollWidth}; }, operation: function(f){return operation(f)();}, - refresh: function(){updateDisplay(true);}, + compoundChange: function(f){return compoundChange(f);}, + refresh: function(){ + updateDisplay(true); + if (scrollbar.scrollHeight > lastScrollTop) + scrollbar.scrollTop = lastScrollTop; + }, getInputField: function(){return input;}, getWrapperElement: function(){return wrapper;}, getScrollerElement: function(){return scroller;}, getGutterElement: function(){return gutter;} }; + function getLine(n) { return getLineAt(doc, n); } + function updateLineHeight(line, height) { + gutterDirty = true; + var diff = height - line.height; + for (var n = line; n; n = n.parent) n.height += diff; + } + function setValue(code) { var top = {line: 0, ch: 0}; - updateLines(top, {line: lines.length - 1, ch: lines[lines.length-1].text.length}, + updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length}, splitLines(code), top, top); updateInput = true; } - function getValue(code) { + function getValue() { var text = []; - for (var i = 0, l = lines.length; i < l; ++i) - text.push(lines[i].text); + doc.iter(0, doc.size, function(line) { text.push(line.text); }); return text.join("\n"); } + function onScroll(e) { + if (lastScrollTop != scrollbar.scrollTop || lastScrollLeft != scroller.scrollLeft) { + lastScrollTop = scrollbar.scrollTop; + lastScrollLeft = scroller.scrollLeft; + updateDisplay([]); + if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + "px"; + if (options.onScroll) options.onScroll(instance); + } + } + function onMouseDown(e) { + setShift(e_prop(e, "shiftKey")); // Check whether this is a click in a widget for (var n = e_target(e); n != wrapper; n = n.parentNode) if (n.parentNode == code && n != mover) return; - // First, see if this is a click in the gutter + // See if this is a click in the gutter for (var n = e_target(e); n != wrapper; n = n.parentNode) if (n.parentNode == gutterText) { if (options.onGutterClick) @@ -277,6 +400,8 @@ var CodeMirror = (function() { return; case 2: if (start) setCursor(start.line, start.ch, true); + setTimeout(focusInput, 20); + e_preventDefault(e); return; } // For button 1, if it was clicked inside the editor @@ -287,29 +412,36 @@ var CodeMirror = (function() { if (!focused) onFocus(); var now = +new Date; - if (lastDoubleClick > now - 400) { + if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) { e_preventDefault(e); + setTimeout(focusInput, 20); return selectLine(start.line); - } else if (lastClick > now - 400) { - lastDoubleClick = now; + } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) { + lastDoubleClick = {time: now, pos: start}; e_preventDefault(e); return selectWordAt(start); - } else { lastClick = now; } + } else { lastClick = {time: now, pos: start}; } var last = start, going; - if (dragAndDrop && !posEq(sel.from, sel.to) && + if (options.dragDrop && dragAndDrop && !options.readOnly && !posEq(sel.from, sel.to) && !posLess(start, sel.from) && !posLess(sel.to, start)) { // Let the drag handler handle this. - var up = connect(targetDocument, "mouseup", operation(function(e2) { + if (webkit) scroller.draggable = true; + function dragEnd(e2) { + if (webkit) scroller.draggable = false; draggingText = false; - up(); + up(); drop(); if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) { e_preventDefault(e2); setCursor(start.line, start.ch, true); focusInput(); } - }), true); + } + var up = connect(document, "mouseup", operation(dragEnd), true); + var drop = connect(scroller, "drop", operation(dragEnd), true); draggingText = true; + // IE's approach to draggable + if (scroller.dragDrop) scroller.dragDrop(); return; } e_preventDefault(e); @@ -328,12 +460,7 @@ var CodeMirror = (function() { } } - var move = connect(targetDocument, "mousemove", operation(function(e) { - clearTimeout(going); - e_preventDefault(e); - extend(e); - }), true); - var up = connect(targetDocument, "mouseup", operation(function(e) { + function done(e) { clearTimeout(going); var cur = posFromMouse(e); if (cur) setSelectionUser(start, cur); @@ -341,16 +468,26 @@ var CodeMirror = (function() { focusInput(); updateInput = true; move(); up(); + } + var move = connect(document, "mousemove", operation(function(e) { + clearTimeout(going); + e_preventDefault(e); + if (!ie && !e_button(e)) done(e); + else extend(e); }), true); + var up = connect(document, "mouseup", operation(done), true); } function onDoubleClick(e) { + for (var n = e_target(e); n != wrapper; n = n.parentNode) + if (n.parentNode == gutterText) return e_preventDefault(e); var start = posFromMouse(e); if (!start) return; - lastDoubleClick = +new Date; + lastDoubleClick = {time: +new Date, pos: start}; e_preventDefault(e); selectWordAt(start); } function onDrop(e) { + if (options.onDragEvent && options.onDragEvent(instance, addStop(e))) return; e.preventDefault(); var pos = posFromMouse(e, true), files = e.dataTransfer.files; if (!pos || options.readOnly) return; @@ -360,102 +497,147 @@ var CodeMirror = (function() { reader.onload = function() { text[i] = reader.result; if (++read == n) { - pos = clipPos(pos); - var end = replaceRange(text.join(""), pos, pos); - setSelectionUser(pos, end); - } + pos = clipPos(pos); + operation(function() { + var end = replaceRange(text.join(""), pos, pos); + setSelectionUser(pos, end); + })(); + } }; reader.readAsText(file); } var n = files.length, text = Array(n), read = 0; for (var i = 0; i < n; ++i) loadFile(files[i], i); - } - else { + } else { + // Don't do a replace if the drop happened inside of the selected text. + if (draggingText && !(posLess(pos, sel.from) || posLess(sel.to, pos))) return; try { var text = e.dataTransfer.getData("Text"); if (text) { - var end = replaceRange(text, pos, pos); - var curFrom = sel.from, curTo = sel.to; - setSelectionUser(pos, end); - if (draggingText) replaceRange("", curFrom, curTo); - focusInput(); - } + compoundChange(function() { + var curFrom = sel.from, curTo = sel.to; + setSelectionUser(pos, pos); + if (draggingText) replaceRange("", curFrom, curTo); + replaceSelection(text); + focusInput(); + }); + } } catch(e){} } } function onDragStart(e) { var txt = getSelection(); - // This will reset escapeElement - htmlEscape(txt); - e.dataTransfer.setDragImage(escapeElement, 0, 0); e.dataTransfer.setData("Text", txt); + + // Use dummy image instead of default browsers image. + if (gecko || chrome || opera) { + var img = document.createElement('img'); + img.scr = 'data:image/gif;base64,R0lGODdhAgACAIAAAAAAAP///ywAAAAAAgACAAACAoRRADs='; //1x1 image + e.dataTransfer.setDragImage(img, 0, 0); + } } + + function doHandleBinding(bound, dropShift) { + if (typeof bound == "string") { + bound = commands[bound]; + if (!bound) return false; + } + var prevShift = shiftSelecting; + try { + if (options.readOnly) suppressEdits = true; + if (dropShift) shiftSelecting = null; + bound(instance); + } catch(e) { + if (e != Pass) throw e; + return false; + } finally { + shiftSelecting = prevShift; + suppressEdits = false; + } + return true; + } + function handleKeyBinding(e) { + // Handle auto keymap transitions + var startMap = getKeyMap(options.keyMap), next = startMap.auto; + clearTimeout(maybeTransition); + if (next && !isModifierKey(e)) maybeTransition = setTimeout(function() { + if (getKeyMap(options.keyMap) == startMap) { + options.keyMap = (next.call ? next.call(null, instance) : next); + } + }, 50); + + var name = keyNames[e_prop(e, "keyCode")], handled = false; + if (name == null || e.altGraphKey) return false; + if (e_prop(e, "altKey")) name = "Alt-" + name; + if (e_prop(e, "ctrlKey")) name = "Ctrl-" + name; + if (e_prop(e, "metaKey")) name = "Cmd-" + name; + + var stopped = false; + function stop() { stopped = true; } + + if (e_prop(e, "shiftKey")) { + handled = lookupKey("Shift-" + name, options.extraKeys, options.keyMap, + function(b) {return doHandleBinding(b, true);}, stop) + || lookupKey(name, options.extraKeys, options.keyMap, function(b) { + if (typeof b == "string" && /^go[A-Z]/.test(b)) return doHandleBinding(b); + }, stop); + } else { + handled = lookupKey(name, options.extraKeys, options.keyMap, doHandleBinding, stop); + } + if (stopped) handled = false; + if (handled) { + e_preventDefault(e); + restartBlink(); + if (ie) { e.oldKeyCode = e.keyCode; e.keyCode = 0; } + } + return handled; + } + function handleCharBinding(e, ch) { + var handled = lookupKey("'" + ch + "'", options.extraKeys, + options.keyMap, function(b) { return doHandleBinding(b, true); }); + if (handled) { + e_preventDefault(e); + restartBlink(); + } + return handled; + } + + var lastStoppedKey = null, maybeTransition; function onKeyDown(e) { if (!focused) onFocus(); - - var code = e.keyCode; + if (ie && e.keyCode == 27) { e.returnValue = false; } + if (pollingFast) { if (readInput()) pollingFast = false; } + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var code = e_prop(e, "keyCode"); // IE does strange things with escape. - if (ie && code == 27) { e.returnValue = false; } - // Tries to detect ctrl on non-mac, cmd on mac. - var mod = (mac ? e.metaKey : e.ctrlKey) && !e.altKey, anyMod = e.ctrlKey || e.altKey || e.metaKey; - if (code == 16 || e.shiftKey) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); - else shiftSelecting = null; + setShift(code == 16 || e_prop(e, "shiftKey")); // First give onKeyEvent option a chance to handle this. - if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - - if (code == 33 || code == 34) {scrollPage(code == 34); return e_preventDefault(e);} // page up/down - if (mod && ((code == 36 || code == 35) || // ctrl-home/end - mac && (code == 38 || code == 40))) { // cmd-up/down - scrollEnd(code == 36 || code == 38); return e_preventDefault(e); + var handled = handleKeyBinding(e); + if (opera) { + lastStoppedKey = handled ? code : null; + // Opera has no cut event... we try to at least catch the key combo + if (!handled && code == 88 && e_prop(e, mac ? "metaKey" : "ctrlKey")) + replaceSelection(""); } - if (mod && code == 65) {selectAll(); return e_preventDefault(e);} // ctrl-a - if (!options.readOnly) { - if (!anyMod && code == 13) {return;} // enter - if (!anyMod && code == 9 && handleTab(e.shiftKey)) return e_preventDefault(e); // tab - if (mod && code == 90) {undo(); return e_preventDefault(e);} // ctrl-z - if (mod && ((e.shiftKey && code == 90) || code == 89)) {redo(); return e_preventDefault(e);} // ctrl-shift-z, ctrl-y + } + function onKeyPress(e) { + if (pollingFast) readInput(); + if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; + var keyCode = e_prop(e, "keyCode"), charCode = e_prop(e, "charCode"); + if (opera && keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;} + if (((opera && (!e.which || e.which < 10)) || khtml) && handleKeyBinding(e)) return; + var ch = String.fromCharCode(charCode == null ? keyCode : charCode); + if (options.electricChars && mode.electricChars && options.smartIndent && !options.readOnly) { + if (mode.electricChars.indexOf(ch) > -1) + setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 75); } - if (code == 36) { if (options.smartHome) { smartHome(); return e_preventDefault(e); } } - - // Key id to use in the movementKeys map. We also pass it to - // fastPoll in order to 'self learn'. We need this because - // reducedSelection, the hack where we collapse the selection to - // its start when it is inverted and a movement key is pressed - // (and later restore it again), shouldn't be used for - // non-movement keys. - curKeyId = (mod ? "c" : "") + (e.altKey ? "a" : "") + code; - if (sel.inverted && movementKeys[curKeyId] === true) { - var range = selRange(input); - if (range) { - reducedSelection = {anchor: range.start}; - setSelRange(input, range.start, range.start); - } - } - // Don't save the key as a movementkey unless it had a modifier - if (!mod && !e.altKey) curKeyId = null; - fastPoll(curKeyId); + if (handleCharBinding(e, ch)) return; + fastPoll(); } function onKeyUp(e) { if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - if (reducedSelection) { - reducedSelection = null; - updateInput = true; - } - if (e.keyCode == 16) shiftSelecting = null; - } - function onKeyPress(e) { - if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return; - if (options.electricChars && mode.electricChars) { - var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode); - if (mode.electricChars.indexOf(ch) > -1) - setTimeout(operation(function() {indentLine(sel.to.line, "smart");}), 50); - } - var code = e.keyCode; - // Re-stop tab and enter. Necessary on some browsers. - if (code == 13) {if (!options.readOnly) handleEnter(); e_preventDefault(e);} - else if (!e.ctrlKey && !e.altKey && !e.metaKey && code == 9 && options.tabMode != "default") e_preventDefault(e); - else fastPoll(curKeyId); + if (e_prop(e, "keyCode") == 16) shiftSelecting = null; } function onFocus() { @@ -463,9 +645,9 @@ var CodeMirror = (function() { if (!focused) { if (options.onFocus) options.onFocus(instance); focused = true; - if (wrapper.className.search(/\bCodeMirror-focused\b/) == -1) - wrapper.className += " CodeMirror-focused"; - if (!leaveInputAlone) prepareInput(); + if (scroller.className.search(/\bCodeMirror-focused\b/) == -1) + scroller.className += " CodeMirror-focused"; + if (!leaveInputAlone) resetInput(true); } slowPoll(); restartBlink(); @@ -474,90 +656,149 @@ var CodeMirror = (function() { if (focused) { if (options.onBlur) options.onBlur(instance); focused = false; - wrapper.className = wrapper.className.replace(" CodeMirror-focused", ""); + if (bracketHighlighted) + operation(function(){ + if (bracketHighlighted) { bracketHighlighted(); bracketHighlighted = null; } + })(); + scroller.className = scroller.className.replace(" CodeMirror-focused", ""); } clearInterval(blinker); setTimeout(function() {if (!focused) shiftSelecting = null;}, 150); } + function chopDelta(delta) { + // Make sure we always scroll a little bit for any nonzero delta. + if (delta > 0.0 && delta < 1.0) return 1; + else if (delta > -1.0 && delta < 0.0) return -1; + else return Math.round(delta); + } + + function onMouseWheel(e) { + var deltaX = 0, deltaY = 0; + if (e.type == "DOMMouseScroll") { // Firefox + var delta = -e.detail * 8.0; + if (e.axis == e.HORIZONTAL_AXIS) deltaX = delta; + else if (e.axis == e.VERTICAL_AXIS) deltaY = delta; + } else if (e.wheelDeltaX !== undefined && e.wheelDeltaY !== undefined) { // WebKit + deltaX = e.wheelDeltaX / 3.0; + deltaY = e.wheelDeltaY / 3.0; + } else if (e.wheelDelta !== undefined) { // IE or Opera + deltaY = e.wheelDelta / 3.0; + } + + var scrolled = false; + deltaX = chopDelta(deltaX); + deltaY = chopDelta(deltaY); + if ((deltaX > 0 && scroller.scrollLeft > 0) || + (deltaX < 0 && scroller.scrollLeft + scroller.clientWidth < scroller.scrollWidth)) { + scroller.scrollLeft -= deltaX; + scrolled = true; + } + if ((deltaY > 0 && scrollbar.scrollTop > 0) || + (deltaY < 0 && scrollbar.scrollTop + scrollbar.clientHeight < scrollbar.scrollHeight)) { + scrollbar.scrollTop -= deltaY; + scrolled = true; + } + if (scrolled) e_stop(e); + } + // Replace the range from from to to by the strings in newText. // Afterwards, set the selection to selFrom, selTo. function updateLines(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; if (history) { var old = []; - for (var i = from.line, e = to.line + 1; i < e; ++i) old.push(lines[i].text); + doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); }); history.addChange(from.line, newText.length, old); while (history.done.length > options.undoDepth) history.done.shift(); } updateLinesNoUndo(from, to, newText, selFrom, selTo); } function unredoHelper(from, to) { - var change = from.pop(); - if (change) { + if (!from.length) return; + var set = from.pop(), out = []; + for (var i = set.length - 1; i >= 0; i -= 1) { + var change = set[i]; var replaced = [], end = change.start + change.added; - for (var i = change.start; i < end; ++i) replaced.push(lines[i].text); - to.push({start: change.start, added: change.old.length, old: replaced}); - var pos = clipPos({line: change.start + change.old.length - 1, - ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}); - updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: lines[end-1].text.length}, change.old, pos, pos); - updateInput = true; + doc.iter(change.start, end, function(line) { replaced.push(line.text); }); + out.push({start: change.start, added: change.old.length, old: replaced}); + var pos = {line: change.start + change.old.length - 1, + ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])}; + updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos); } + updateInput = true; + to.push(out); } function undo() {unredoHelper(history.done, history.undone);} function redo() {unredoHelper(history.undone, history.done);} function updateLinesNoUndo(from, to, newText, selFrom, selTo) { + if (suppressEdits) return; var recomputeMaxLength = false, maxLineLength = maxLine.length; - for (var i = from.line; i <= to.line; ++i) { - if (lines[i].text.length == maxLineLength) {recomputeMaxLength = true; break;} - } + if (!options.lineWrapping) + doc.iter(from.line, to.line + 1, function(line) { + if (!line.hidden && line.text.length == maxLineLength) {recomputeMaxLength = true; return true;} + }); + if (from.line != to.line || newText.length > 1) gutterDirty = true; - var nlines = to.line - from.line, firstLine = lines[from.line], lastLine = lines[to.line]; + var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line); // First adjust the line structure, taking some care to leave highlighting intact. - if (firstLine == lastLine) { + if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == "") { + // This is a whole-line replace. Treated specially to make + // sure line objects move the way they are supposed to. + var added = [], prevLine = null; + if (from.line) { + prevLine = getLine(from.line - 1); + prevLine.fixMarkEnds(lastLine); + } else lastLine.fixMarkStarts(); + for (var i = 0, e = newText.length - 1; i < e; ++i) + added.push(Line.inheritMarks(newText[i], prevLine)); + if (nlines) doc.remove(from.line, nlines, callbacks); + if (added.length) doc.insert(from.line, added); + } else if (firstLine == lastLine) { if (newText.length == 1) firstLine.replace(from.ch, to.ch, newText[0]); else { lastLine = firstLine.split(to.ch, newText[newText.length-1]); - var spliceargs = [from.line + 1, nlines]; firstLine.replace(from.ch, null, newText[0]); + firstLine.fixMarkEnds(lastLine); + var added = []; for (var i = 1, e = newText.length - 1; i < e; ++i) - spliceargs.push(Line.inheritMarks(newText[i], firstLine)); - spliceargs.push(lastLine); - lines.splice.apply(lines, spliceargs); + added.push(Line.inheritMarks(newText[i], firstLine)); + added.push(lastLine); + doc.insert(from.line + 1, added); } - } - else if (newText.length == 1) { + } else if (newText.length == 1) { firstLine.replace(from.ch, null, newText[0]); lastLine.replace(null, to.ch, ""); firstLine.append(lastLine); - lines.splice(from.line + 1, nlines); - } - else { - var spliceargs = [from.line + 1, nlines - 1]; + doc.remove(from.line + 1, nlines, callbacks); + } else { + var added = []; firstLine.replace(from.ch, null, newText[0]); lastLine.replace(null, to.ch, newText[newText.length-1]); + firstLine.fixMarkEnds(lastLine); for (var i = 1, e = newText.length - 1; i < e; ++i) - spliceargs.push(Line.inheritMarks(newText[i], firstLine)); - lines.splice.apply(lines, spliceargs); + added.push(Line.inheritMarks(newText[i], firstLine)); + if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks); + doc.insert(from.line + 1, added); } - - - for (var i = from.line, e = i + newText.length; i < e; ++i) { - var l = lines[i].text; - if (l.length > maxLineLength) { - maxLine = l; maxLineLength = l.length; maxWidth = null; - recomputeMaxLength = false; - } - } - if (recomputeMaxLength) { - maxLineLength = 0; maxLine = ""; maxWidth = null; - for (var i = 0, e = lines.length; i < e; ++i) { - var l = lines[i].text; - if (l.length > maxLineLength) { - maxLineLength = l.length; maxLine = l; + if (options.lineWrapping) { + var perLine = Math.max(5, scroller.clientWidth / charWidth() - 3); + doc.iter(from.line, from.line + newText.length, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != line.height) updateLineHeight(line, guess); + }); + } else { + doc.iter(from.line, from.line + newText.length, function(line) { + var l = line.text; + if (!line.hidden && l.length > maxLineLength) { + maxLine = l; maxLineLength = l.length; maxLineChanged = true; + recomputeMaxLength = false; } - } + }); + if (recomputeMaxLength) updateMaxLine = true; } // Add these lines to the work array, so that they will be @@ -568,24 +809,64 @@ var CodeMirror = (function() { if (task < from.line) newWork.push(task); else if (task > to.line) newWork.push(task + lendiff); } - if (newText.length < 5) { - highlightLines(from.line, from.line + newText.length); - newWork.push(from.line + newText.length); - } else { - newWork.push(from.line); - } + var hlEnd = from.line + Math.min(newText.length, 500); + highlightLines(from.line, hlEnd); + newWork.push(hlEnd); work = newWork; startWorker(100); // Remember that these lines changed, for updating the display changes.push({from: from.line, to: to.line + 1, diff: lendiff}); - textChanged = {from: from, to: to, text: newText}; + var changeObj = {from: from, to: to, text: newText}; + if (textChanged) { + for (var cur = textChanged; cur.next; cur = cur.next) {} + cur.next = changeObj; + } else textChanged = changeObj; // Update the selection function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;} - setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line)); + setSelection(clipPos(selFrom), clipPos(selTo), + updateLine(sel.from.line), updateLine(sel.to.line)); + } - // Make sure the scroll-size div has the correct height. - code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px"; + function updateVerticalScroll(scrollTop) { + var th = textHeight(), virtualHeight = Math.floor(doc.height * th + 2 * paddingTop()), scrollbarHeight = scroller.clientHeight; + scrollbar.style.height = scrollbarHeight + "px"; + if (scroller.clientHeight) + scrollbarInner.style.height = virtualHeight + "px"; + // Position the mover div to align with the current virtual scroll position + if (scrollTop != null) scrollbar.scrollTop = scrollTop; + mover.style.top = (displayOffset * th - scrollbar.scrollTop) + "px"; + scrollbar.style.display = (virtualHeight > scrollbarHeight) ? "block" : "none"; + } + + // On Mac OS X Lion and up, detect whether the mouse is plugged in by measuring + // the width of a div with a scrollbar in it. If the width is <= 1, then + // the mouse isn't plugged in and scrollbars should overlap the content. + function overlapScrollbars() { + var tmpSb = document.createElement('div'), + tmpSbInner = document.createElement('div'); + tmpSb.className = "CodeMirror-scrollbar"; + tmpSb.style.cssText = "position: absolute; left: -9999px; height: 100px;"; + tmpSbInner.className = "CodeMirror-scrollbar-inner"; + tmpSbInner.style.height = "200px"; + tmpSb.appendChild(tmpSbInner); + + document.body.appendChild(tmpSb); + var result = (tmpSb.offsetWidth <= 1); + document.body.removeChild(tmpSb); + return result; + } + + function computeMaxLength() { + var maxLineLength = 0; + maxLine = ""; maxLineChanged = true; + doc.iter(0, doc.size, function(line) { + var l = line.text; + if (!line.hidden && l.length > maxLineLength) { + maxLineLength = l.length; maxLine = l; + } + }); + updateMaxLine = false; } function replaceRange(code, from, to) { @@ -623,10 +904,10 @@ var CodeMirror = (function() { function getRange(from, to) { var l1 = from.line, l2 = to.line; - if (l1 == l2) return lines[l1].text.slice(from.ch, to.ch); - var code = [lines[l1].text.slice(from.ch)]; - for (var i = l1 + 1; i < l2; ++i) code.push(lines[i].text); - code.push(lines[l2].text.slice(0, to.ch)); + if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch); + var code = [getLine(l1).text.slice(from.ch)]; + doc.iter(l1 + 1, l2, function(line) { code.push(line.text); }); + code.push(getLine(l2).text.slice(0, to.ch)); return code.join("\n"); } function getSelection() { @@ -636,115 +917,56 @@ var CodeMirror = (function() { var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll function slowPoll() { if (pollingFast) return; - poll.set(2000, function() { + poll.set(options.pollInterval, function() { startOperation(); readInput(); if (focused) slowPoll(); endOperation(); }); } - function fastPoll(keyId) { + function fastPoll() { var missed = false; pollingFast = true; function p() { startOperation(); var changed = readInput(); - if (changed && keyId) { - if (changed == "moved" && movementKeys[keyId] == null) movementKeys[keyId] = true; - if (changed == "changed") movementKeys[keyId] = false; - } - if (!changed && !missed) {missed = true; poll.set(80, p);} + if (!changed && !missed) {missed = true; poll.set(60, p);} else {pollingFast = false; slowPoll();} endOperation(); } poll.set(20, p); } - // Inspects the textarea, compares its state (content, selection) - // to the data in the editing variable, and updates the editor - // content or cursor if something changed. + // Previnput is a hack to work with IME. If we reset the textarea + // on every change, that breaks IME. So we look for changes + // compared to the previous content instead. (Modern browsers have + // events that indicate IME taking place, but these are not widely + // supported or compatible enough yet to rely on.) + var prevInput = ""; function readInput() { - if (leaveInputAlone || !focused) return; - var changed = false, text = input.value, sr = selRange(input); - if (!sr) return false; - var changed = editing.text != text, rs = reducedSelection; - var moved = changed || sr.start != editing.start || sr.end != (rs ? editing.start : editing.end); - if (!moved && !rs) return false; - if (changed) { - shiftSelecting = reducedSelection = null; - if (options.readOnly) {updateInput = true; return "changed";} - } - - // Compute selection start and end based on start/end offsets in textarea - function computeOffset(n, startLine) { - var pos = 0; - for (;;) { - var found = text.indexOf("\n", pos); - if (found == -1 || (text.charAt(found-1) == "\r" ? found - 1 : found) >= n) - return {line: startLine, ch: n - pos}; - ++startLine; - pos = found + 1; - } - } - var from = computeOffset(sr.start, editing.from), - to = computeOffset(sr.end, editing.from); - // Here we have to take the reducedSelection hack into account, - // so that you can, for example, press shift-up at the start of - // your selection and have the right thing happen. - if (rs) { - var head = sr.start == rs.anchor ? to : from; - var tail = shiftSelecting ? sel.to : sr.start == rs.anchor ? from : to; - if (sel.inverted = posLess(head, tail)) { from = head; to = tail; } - else { reducedSelection = null; from = tail; to = head; } - } - - // In some cases (cursor on same line as before), we don't have - // to update the textarea content at all. - if (from.line == to.line && from.line == sel.from.line && from.line == sel.to.line && !shiftSelecting) - updateInput = false; - - // Magic mess to extract precise edited range from the changed - // string. - if (changed) { - var start = 0, end = text.length, len = Math.min(end, editing.text.length); - var c, line = editing.from, nl = -1; - while (start < len && (c = text.charAt(start)) == editing.text.charAt(start)) { - ++start; - if (c == "\n") {line++; nl = start;} - } - var ch = nl > -1 ? start - nl : start, endline = editing.to - 1, edend = editing.text.length; - for (;;) { - c = editing.text.charAt(edend); - if (text.charAt(end) != c) {++end; ++edend; break;} - if (c == "\n") endline--; - if (edend <= start || end <= start) break; - --end; --edend; - } - var nl = editing.text.lastIndexOf("\n", edend - 1), endch = nl == -1 ? edend : edend - nl - 1; - updateLines({line: line, ch: ch}, {line: endline, ch: endch}, splitLines(text.slice(start, end)), from, to); - if (line != endline || from.line != line) updateInput = true; - } - else setSelection(from, to); - - editing.text = text; editing.start = sr.start; editing.end = sr.end; - return changed ? "changed" : moved ? "moved" : false; + if (leaveInputAlone || !focused || hasSelection(input) || options.readOnly) return false; + var text = input.value; + if (text == prevInput) return false; + shiftSelecting = null; + var same = 0, l = Math.min(prevInput.length, text.length); + while (same < l && prevInput[same] == text[same]) ++same; + if (same < prevInput.length) + sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)}; + else if (overwrite && posEq(sel.from, sel.to)) + sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))}; + replaceSelection(text.slice(same), "end"); + if (text.length > 1000) { input.value = prevInput = ""; } + else prevInput = text; + return true; + } + function resetInput(user) { + if (!posEq(sel.from, sel.to)) { + prevInput = ""; + input.value = getSelection(); + selectInput(input); + } else if (user) prevInput = input.value = ""; } - // Set the textarea content and selection range to match the - // editor state. - function prepareInput() { - var text = []; - var from = Math.max(0, sel.from.line - 1), to = Math.min(lines.length, sel.to.line + 2); - for (var i = from; i < to; ++i) text.push(lines[i].text); - text = input.value = text.join(lineSep); - var startch = sel.from.ch, endch = sel.to.ch; - for (var i = from; i < sel.from.line; ++i) - startch += lineSep.length + lines[i].text.length; - for (var i = from; i < sel.to.line; ++i) - endch += lineSep.length + lines[i].text.length; - editing = {text: text, from: from, to: to, start: startch, end: endch}; - setSelRange(input, startch, reducedSelection ? startch : endch); - } function focusInput() { if (options.readOnly != "nocursor") input.focus(); } @@ -752,59 +974,156 @@ var CodeMirror = (function() { function scrollEditorIntoView() { if (!cursor.getBoundingClientRect) return; var rect = cursor.getBoundingClientRect(); + // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden + if (ie && rect.top == rect.bottom) return; var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight); - if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView(); + if (rect.top < 0 || rect.bottom > winH) scrollCursorIntoView(); } function scrollCursorIntoView() { + var coords = calculateCursorCoords(); + return scrollIntoView(coords.x, coords.y, coords.x, coords.yBot); + } + function calculateCursorCoords() { var cursor = localCoords(sel.inverted ? sel.from : sel.to); - return scrollIntoView(cursor.x, cursor.y, cursor.x, cursor.yBot); + var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x; + return {x: x, y: cursor.y, yBot: cursor.yBot}; } function scrollIntoView(x1, y1, x2, y2) { - var pl = paddingLeft(), pt = paddingTop(), lh = lineHeight(); + var scrollPos = calculateScrollPos(x1, y1, x2, y2), scrolled = false; + if (scrollPos.scrollLeft != null) {scroller.scrollLeft = scrollPos.scrollLeft; scrolled = true;} + if (scrollPos.scrollTop != null) {scrollbar.scrollTop = scrollPos.scrollTop; scrolled = true;} + if (scrolled && options.onScroll) options.onScroll(instance); + } + function calculateScrollPos(x1, y1, x2, y2) { + var pl = paddingLeft(), pt = paddingTop(); y1 += pt; y2 += pt; x1 += pl; x2 += pl; - var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true; - if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;} - else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;} + var screen = scroller.clientHeight, screentop = scrollbar.scrollTop, result = {}; + var atTop = y1 < paddingTop() + 10; + if (y1 < screentop) result.scrollTop = atTop ? 0 : Math.max(0, y1); + else if (y2 > screentop + screen) result.scrollTop = y2 - screen; var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft; var gutterw = options.fixedGutter ? gutter.clientWidth : 0; - if (x1 < screenleft + gutterw) { - if (x1 < 50) x1 = 0; - scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw); - scrolled = true; + var atLeft = x1 < gutterw + pl + 10; + if (x1 < screenleft + gutterw || atLeft) { + if (atLeft) x1 = 0; + result.scrollLeft = Math.max(0, x1 - 10 - gutterw); + } else if (x2 > screenw + screenleft - 3) { + result.scrollLeft = x2 + 10 - screenw; } - else if (x2 > screenw + screenleft) { - scroller.scrollLeft = x2 + 10 - screenw; - scrolled = true; - if (x2 > code.clientWidth) result = false; - } - if (scrolled && options.onScroll) options.onScroll(instance); return result; } - function visibleLines() { - var lh = lineHeight(), top = scroller.scrollTop - paddingTop(); - return {from: Math.min(lines.length, Math.max(0, Math.floor(top / lh))), - to: Math.min(lines.length, Math.ceil((top + scroller.clientHeight) / lh))}; + function visibleLines(scrollTop) { + var lh = textHeight(), top = (scrollTop != null ? scrollTop : scrollbar.scrollTop) - paddingTop(); + var fromHeight = Math.max(0, Math.floor(top / lh)); + var toHeight = Math.ceil((top + scroller.clientHeight) / lh); + return {from: lineAtHeight(doc, fromHeight), + to: lineAtHeight(doc, toHeight)}; } // Uses a set of changes plus the current scroll position to // determine which DOM updates have to be made, and makes the // updates. - function updateDisplay(changes) { + function updateDisplay(changes, suppressCallback, scrollTop) { if (!scroller.clientWidth) { - showingFrom = showingTo = 0; + showingFrom = showingTo = displayOffset = 0; + return; + } + // Compute the new visible window + // If scrollTop is specified, use that to determine which lines + // to render instead of the current scrollbar position. + var visible = visibleLines(scrollTop); + // Bail out if the visible area is already rendered and nothing changed. + if (changes !== true && changes.length == 0 && visible.from > showingFrom && visible.to < showingTo) { + updateVerticalScroll(scrollTop); + return; + } + var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100); + if (showingFrom < from && from - showingFrom < 20) from = showingFrom; + if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo); + + // Create a range of theoretically intact lines, and punch holes + // in that using the change info. + var intact = changes === true ? [] : + computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes); + // Clip off the parts that won't be visible + var intactLines = 0; + for (var i = 0; i < intact.length; ++i) { + var range = intact[i]; + if (range.from < from) {range.domStart += (from - range.from); range.from = from;} + if (range.to > to) range.to = to; + if (range.from >= range.to) intact.splice(i--, 1); + else intactLines += range.to - range.from; + } + if (intactLines == to - from && from == showingFrom && to == showingTo) { + updateVerticalScroll(scrollTop); return; } - // First create a range of theoretically intact lines, and punch - // holes in that using the change info. - var intact = changes === true ? [] : [{from: showingFrom, to: showingTo, domStart: 0}]; + intact.sort(function(a, b) {return a.domStart - b.domStart;}); + + var th = textHeight(), gutterDisplay = gutter.style.display; + lineDiv.style.display = "none"; + patchDisplay(from, to, intact); + lineDiv.style.display = gutter.style.display = ""; + + var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th; + // This is just a bogus formula that detects when the editor is + // resized or the font size changes. + if (different) lastSizeC = scroller.clientHeight + th; + showingFrom = from; showingTo = to; + displayOffset = heightAtLine(doc, from); + + // Since this is all rather error prone, it is honoured with the + // only assertion in the whole file. + if (lineDiv.childNodes.length != showingTo - showingFrom) + throw new Error("BAD PATCH! " + JSON.stringify(intact) + " size=" + (showingTo - showingFrom) + + " nodes=" + lineDiv.childNodes.length); + + function checkHeights() { + var curNode = lineDiv.firstChild, heightChanged = false; + doc.iter(showingFrom, showingTo, function(line) { + if (!line.hidden) { + var height = Math.round(curNode.offsetHeight / th) || 1; + if (line.height != height) { + updateLineHeight(line, height); + gutterDirty = heightChanged = true; + } + } + curNode = curNode.nextSibling; + }); + return heightChanged; + } + + if (options.lineWrapping) { + // Guess whether we're going to need the scrollbar, so that we don't end up changing the linewrapping + // after the scrollbar appears (during updateVerticalScroll()). Only do this if the scrollbar is + // appearing (if it's disappearing, we don't have to worry about the scroll position, and there are + // issues on IE7 if we turn it off too early). + var virtualHeight = Math.floor(doc.height * th + 2 * paddingTop()), scrollbarHeight = scroller.clientHeight; + if (virtualHeight > scrollbarHeight) scrollbar.style.display = "block"; + checkHeights(); + } + + gutter.style.display = gutterDisplay; + if (different || gutterDirty) { + // If the gutter grew in size, re-check heights. If those changed, re-draw gutter. + updateGutter() && options.lineWrapping && checkHeights() && updateGutter(); + } + updateSelection(); + updateVerticalScroll(scrollTop); + if (!suppressCallback && options.onUpdate) options.onUpdate(instance); + return true; + } + + function computeIntact(intact, changes) { for (var i = 0, l = changes.length || 0; i < l; ++i) { var change = changes[i], intact2 = [], diff = change.diff || 0; for (var j = 0, l2 = intact.length; j < l2; ++j) { var range = intact[j]; - if (change.to <= range.from) - intact2.push({from: range.from + diff, to: range.to + diff, domStart: range.domStart}); - else if (range.to <= change.from) + if (change.to <= range.from && change.diff) + intact2.push({from: range.from + diff, to: range.to + diff, + domStart: range.domStart}); + else if (change.to <= range.from || change.from >= range.to) intact2.push(range); else { if (change.from > range.from) @@ -816,168 +1135,130 @@ var CodeMirror = (function() { } intact = intact2; } - - // Then, determine which lines we'd want to see, and which - // updates have to be made to get there. - var visible = visibleLines(); - var from = Math.min(showingFrom, Math.max(visible.from - 3, 0)), - to = Math.min(lines.length, Math.max(showingTo, visible.to + 3)), - updates = [], domPos = 0, domEnd = showingTo - showingFrom, pos = from, changedLines = 0; - - for (var i = 0, l = intact.length; i < l; ++i) { - var range = intact[i]; - if (range.to <= from) continue; - if (range.from >= to) break; - if (range.domStart > domPos || range.from > pos) { - updates.push({from: pos, to: range.from, domSize: range.domStart - domPos, domStart: domPos}); - changedLines += range.from - pos; - } - pos = range.to; - domPos = range.domStart + (range.to - range.from); - } - if (domPos != domEnd || pos != to) { - changedLines += Math.abs(to - pos); - updates.push({from: pos, to: to, domSize: domEnd - domPos, domStart: domPos}); - if (to - pos != domEnd - domPos) gutterDirty = true; - } - - if (!updates.length) return; - lineDiv.style.display = "none"; - // If more than 30% of the screen needs update, just do a full - // redraw (which is quicker than patching) - if (changedLines > (visible.to - visible.from) * .3) - refreshDisplay(from = Math.max(visible.from - 10, 0), to = Math.min(visible.to + 7, lines.length)); - // Otherwise, only update the stuff that needs updating. - else - patchDisplay(updates); - lineDiv.style.display = ""; - - // Position the mover div to align with the lines it's supposed - // to be showing (which will cover the visible display) - var different = from != showingFrom || to != showingTo || lastHeight != scroller.clientHeight; - showingFrom = from; showingTo = to; - mover.style.top = (from * lineHeight()) + "px"; - if (different) { - lastHeight = scroller.clientHeight; - code.style.height = (lines.length * lineHeight() + 2 * paddingTop()) + "px"; - } - if (different || gutterDirty) updateGutter(); - - if (maxWidth == null) maxWidth = stringWidth(maxLine); - if (maxWidth > scroller.clientWidth) { - lineSpace.style.width = maxWidth + "px"; - // Needed to prevent odd wrapping/hiding of widgets placed in here. - code.style.width = ""; - code.style.width = scroller.scrollWidth + "px"; - } else { - lineSpace.style.width = code.style.width = ""; - } - - // Since this is all rather error prone, it is honoured with the - // only assertion in the whole file. - if (lineDiv.childNodes.length != showingTo - showingFrom) - throw new Error("BAD PATCH! " + JSON.stringify(updates) + " size=" + (showingTo - showingFrom) + - " nodes=" + lineDiv.childNodes.length); - updateCursor(); + return intact; } - function refreshDisplay(from, to) { - var html = [], start = {line: from, ch: 0}, inSel = posLess(sel.from, start) && !posLess(sel.to, start); - for (var i = from; i < to; ++i) { - var ch1 = null, ch2 = null; - if (inSel) { - ch1 = 0; - if (sel.to.line == i) {inSel = false; ch2 = sel.to.ch;} + function patchDisplay(from, to, intact) { + // The first pass removes the DOM nodes that aren't intact. + if (!intact.length) lineDiv.innerHTML = ""; + else { + function killNode(node) { + var tmp = node.nextSibling; + node.parentNode.removeChild(node); + return tmp; } - else if (sel.from.line == i) { - if (sel.to.line == i) {ch1 = sel.from.ch; ch2 = sel.to.ch;} - else {inSel = true; ch1 = sel.from.ch;} + var domPos = 0, curNode = lineDiv.firstChild, n; + for (var i = 0; i < intact.length; ++i) { + var cur = intact[i]; + while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;} + for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;} } - html.push(lines[i].getHTML(ch1, ch2, true)); + while (curNode) curNode = killNode(curNode); } - lineDiv.innerHTML = html.join(""); - } - function patchDisplay(updates) { - // Slightly different algorithm for IE (badInnerHTML), since - // there .innerHTML on PRE nodes is dumb, and discards - // whitespace. - var sfrom = sel.from.line, sto = sel.to.line, off = 0, - scratch = badInnerHTML && targetDocument.createElement("div"); - for (var i = 0, e = updates.length; i < e; ++i) { - var rec = updates[i]; - var extra = (rec.to - rec.from) - rec.domSize; - var nodeAfter = lineDiv.childNodes[rec.domStart + rec.domSize + off] || null; - if (badInnerHTML) - for (var j = Math.max(-extra, rec.domSize); j > 0; --j) - lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); - else if (extra) { - for (var j = Math.max(0, extra); j > 0; --j) - lineDiv.insertBefore(targetDocument.createElement("pre"), nodeAfter); - for (var j = Math.max(0, -extra); j > 0; --j) - lineDiv.removeChild(nodeAfter ? nodeAfter.previousSibling : lineDiv.lastChild); + // This pass fills in the lines that actually changed. + var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from; + var scratch = document.createElement("div"); + doc.iter(from, to, function(line) { + if (nextIntact && nextIntact.to == j) nextIntact = intact.shift(); + if (!nextIntact || nextIntact.from > j) { + if (line.hidden) var html = scratch.innerHTML = "
";
+          else {
+            var html = ''
+              + line.getHTML(makeTab) + '';
+            // Kludge to make sure the styled element lies behind the selection (by z-index)
+            if (line.bgClassName)
+              html = '
 
' + html + "
"; + } + scratch.innerHTML = html; + lineDiv.insertBefore(scratch.firstChild, curNode); + } else { + curNode = curNode.nextSibling; } - var node = lineDiv.childNodes[rec.domStart + off], inSel = sfrom < rec.from && sto >= rec.from; - for (var j = rec.from; j < rec.to; ++j) { - var ch1 = null, ch2 = null; - if (inSel) { - ch1 = 0; - if (sto == j) {inSel = false; ch2 = sel.to.ch;} - } - else if (sfrom == j) { - if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;} - else {inSel = true; ch1 = sel.from.ch;} - } - if (badInnerHTML) { - scratch.innerHTML = lines[j].getHTML(ch1, ch2, true); - lineDiv.insertBefore(scratch.firstChild, nodeAfter); - } - else { - node.innerHTML = lines[j].getHTML(ch1, ch2, false); - node.className = lines[j].className || ""; - node = node.nextSibling; - } - } - off += extra; - } + ++j; + }); } function updateGutter() { if (!options.gutter && !options.lineNumbers) return; var hText = mover.offsetHeight, hEditor = scroller.clientHeight; gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + "px"; - var html = []; - for (var i = showingFrom; i < Math.max(showingTo, showingFrom + 1); ++i) { - var marker = lines[i].gutterMarker; - var text = options.lineNumbers ? i + options.firstLineNumber : null; - if (marker && marker.text) - text = marker.text.replace("%N%", text != null ? text : ""); - else if (text == null) - text = "\u00a0"; - html.push((marker && marker.style ? '
' : "
"), text, "
"); - } + var html = [], i = showingFrom, normalNode; + doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) { + if (line.hidden) { + html.push("
");
+        } else {
+          var marker = line.gutterMarker;
+          var text = options.lineNumbers ? i + options.firstLineNumber : null;
+          if (marker && marker.text)
+            text = marker.text.replace("%N%", text != null ? text : "");
+          else if (text == null)
+            text = "\u00a0";
+          html.push((marker && marker.style ? '
' : "
"), text);
+          for (var j = 1; j < line.height; ++j) html.push("
 "); + html.push("
"); + if (!marker) normalNode = i; + } + ++i; + }); gutter.style.display = "none"; gutterText.innerHTML = html.join(""); - var minwidth = String(lines.length).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = ""; - while (val.length + pad.length < minwidth) pad += "\u00a0"; - if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild); + // Make sure scrolling doesn't cause number gutter size to pop + if (normalNode != null && options.lineNumbers) { + var node = gutterText.childNodes[normalNode - showingFrom]; + var minwidth = String(doc.size).length, val = eltText(node.firstChild), pad = ""; + while (val.length + pad.length < minwidth) pad += "\u00a0"; + if (pad) node.insertBefore(document.createTextNode(pad), node.firstChild); + } gutter.style.display = ""; + var resized = Math.abs((parseInt(lineSpace.style.marginLeft) || 0) - gutter.offsetWidth) > 2; lineSpace.style.marginLeft = gutter.offsetWidth + "px"; gutterDirty = false; + return resized; } - function updateCursor() { - var head = sel.inverted ? sel.from : sel.to, lh = lineHeight(); - var x = charX(head.line, head.ch); - var top = head.line * lh - scroller.scrollTop; - inputDiv.style.top = Math.max(Math.min(top, scroller.offsetHeight), 0) + "px"; - inputDiv.style.left = (x - scroller.scrollLeft) + "px"; - if (posEq(sel.from, sel.to)) { - cursor.style.top = (head.line - showingFrom) * lh + "px"; - cursor.style.left = x + "px"; + function updateSelection() { + var collapsed = posEq(sel.from, sel.to); + var fromPos = localCoords(sel.from, true); + var toPos = collapsed ? fromPos : localCoords(sel.to, true); + var headPos = sel.inverted ? fromPos : toPos, th = textHeight(); + var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv); + inputDiv.style.top = Math.max(0, Math.min(scroller.offsetHeight, headPos.y + lineOff.top - wrapOff.top)) + "px"; + inputDiv.style.left = Math.max(0, Math.min(scroller.offsetWidth, headPos.x + lineOff.left - wrapOff.left)) + "px"; + if (collapsed) { + cursor.style.top = headPos.y + "px"; + cursor.style.left = (options.lineWrapping ? Math.min(headPos.x, lineSpace.offsetWidth) : headPos.x) + "px"; cursor.style.display = ""; + selectionDiv.style.display = "none"; + } else { + var sameLine = fromPos.y == toPos.y, html = ""; + var clientWidth = lineSpace.clientWidth || lineSpace.offsetWidth; + var clientHeight = lineSpace.clientHeight || lineSpace.offsetHeight; + function add(left, top, right, height) { + var rstyle = quirksMode ? "width: " + (!right ? clientWidth : clientWidth - right - left) + "px" + : "right: " + right + "px"; + html += '
'; + } + if (sel.from.ch && fromPos.y >= 0) { + var right = sameLine ? clientWidth - toPos.x : 0; + add(fromPos.x, fromPos.y, right, th); + } + var middleStart = Math.max(0, fromPos.y + (sel.from.ch ? th : 0)); + var middleHeight = Math.min(toPos.y, clientHeight) - middleStart; + if (middleHeight > 0.2 * th) + add(0, middleStart, 0, middleHeight); + if ((!sameLine || !sel.from.ch) && toPos.y < clientHeight - .5 * th) + add(0, toPos.y, clientWidth - toPos.x, th); + selectionDiv.innerHTML = html; + cursor.style.display = "none"; + selectionDiv.style.display = ""; } - else cursor.style.display = "none"; } + function setShift(val) { + if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from); + else shiftSelecting = null; + } function setSelectionUser(from, to) { var sh = shiftSelecting && clipPos(shiftSelecting); if (sh) { @@ -985,130 +1266,166 @@ var CodeMirror = (function() { else if (posLess(to, sh)) to = sh; } setSelection(from, to); + userSelChange = true; } // Update the selection. Last two args are only used by // updateLines, since they have to be expressed in the line // numbers before the update. function setSelection(from, to, oldFrom, oldTo) { + goalColumn = null; + if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} if (posEq(sel.from, from) && posEq(sel.to, to)) return; if (posLess(to, from)) {var tmp = to; to = from; from = tmp;} + // Skip over hidden lines. + if (from.line != oldFrom) { + var from1 = skipHidden(from, oldFrom, sel.from.ch); + // If there is no non-hidden line left, force visibility on current line + if (!from1) setLineHidden(from.line, false); + else from = from1; + } + if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch); + if (posEq(from, to)) sel.inverted = false; else if (posEq(from, sel.to)) sel.inverted = false; else if (posEq(to, sel.from)) sel.inverted = true; - // Some ugly logic used to only mark the lines that actually did - // see a change in selection as changed, rather than the whole - // selected range. - if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;} - if (posEq(from, to)) { - if (!posEq(sel.from, sel.to)) - changes.push({from: oldFrom, to: oldTo + 1}); - } - else if (posEq(sel.from, sel.to)) { - changes.push({from: from.line, to: to.line + 1}); - } - else { - if (!posEq(from, sel.from)) { - if (from.line < oldFrom) - changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1}); - else - changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1}); - } - if (!posEq(to, sel.to)) { - if (to.line < oldTo) - changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1}); - else - changes.push({from: Math.max(from.line, oldTo), to: to.line + 1}); + if (options.autoClearEmptyLines && posEq(sel.from, sel.to)) { + var head = sel.inverted ? from : to; + if (head.line != sel.from.line && sel.from.line < doc.size) { + var oldLine = getLine(sel.from.line); + if (/^\s+$/.test(oldLine.text)) + setTimeout(operation(function() { + if (oldLine.parent && /^\s+$/.test(oldLine.text)) { + var no = lineNo(oldLine); + replaceRange("", {line: no, ch: 0}, {line: no, ch: oldLine.text.length}); + } + }, 10)); } } + sel.from = from; sel.to = to; selectionChanged = true; } + function skipHidden(pos, oldLine, oldCh) { + function getNonHidden(dir) { + var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1; + while (lNo != end) { + var line = getLine(lNo); + if (!line.hidden) { + var ch = pos.ch; + if (toEnd || ch > oldCh || ch > line.text.length) ch = line.text.length; + return {line: lNo, ch: ch}; + } + lNo += dir; + } + } + var line = getLine(pos.line); + var toEnd = pos.ch == line.text.length && pos.ch != oldCh; + if (!line.hidden) return pos; + if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1); + else return getNonHidden(-1) || getNonHidden(1); + } function setCursor(line, ch, user) { var pos = clipPos({line: line, ch: ch || 0}); (user ? setSelectionUser : setSelection)(pos, pos); } - function clipLine(n) {return Math.max(0, Math.min(n, lines.length-1));} + function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));} function clipPos(pos) { if (pos.line < 0) return {line: 0, ch: 0}; - if (pos.line >= lines.length) return {line: lines.length-1, ch: lines[lines.length-1].text.length}; - var ch = pos.ch, linelen = lines[pos.line].text.length; + if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length}; + var ch = pos.ch, linelen = getLine(pos.line).text.length; if (ch == null || ch > linelen) return {line: pos.line, ch: linelen}; else if (ch < 0) return {line: pos.line, ch: 0}; else return pos; } - function scrollPage(down) { - var linesPerPage = Math.floor(scroller.clientHeight / lineHeight()), head = sel.inverted ? sel.from : sel.to; - setCursor(head.line + (Math.max(linesPerPage - 1, 1) * (down ? 1 : -1)), head.ch, true); - } - function scrollEnd(top) { - var pos = top ? {line: 0, ch: 0} : {line: lines.length - 1, ch: lines[lines.length-1].text.length}; - setSelectionUser(pos, pos); + function findPosH(dir, unit) { + var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch; + var lineObj = getLine(line); + function findNextLine() { + for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) { + var lo = getLine(l); + if (!lo.hidden) { line = l; lineObj = lo; return true; } + } + } + function moveOnce(boundToLine) { + if (ch == (dir < 0 ? 0 : lineObj.text.length)) { + if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0; + else return false; + } else ch += dir; + return true; + } + if (unit == "char") moveOnce(); + else if (unit == "column") moveOnce(true); + else if (unit == "word") { + var sawWord = false; + for (;;) { + if (dir < 0) if (!moveOnce()) break; + if (isWordChar(lineObj.text.charAt(ch))) sawWord = true; + else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;} + if (dir > 0) if (!moveOnce()) break; + } + } + return {line: line, ch: ch}; } - function selectAll() { - var endLine = lines.length - 1; - setSelection({line: 0, ch: 0}, {line: endLine, ch: lines[endLine].text.length}); + function moveH(dir, unit) { + var pos = dir < 0 ? sel.from : sel.to; + if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit); + setCursor(pos.line, pos.ch, true); + } + function deleteH(dir, unit) { + if (!posEq(sel.from, sel.to)) replaceRange("", sel.from, sel.to); + else if (dir < 0) replaceRange("", findPosH(dir, unit), sel.to); + else replaceRange("", sel.from, findPosH(dir, unit)); + userSelChange = true; } + var goalColumn = null; + function moveV(dir, unit) { + var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true); + if (goalColumn != null) pos.x = goalColumn; + if (unit == "page") dist = Math.min(scroller.clientHeight, window.innerHeight || document.documentElement.clientHeight); + else if (unit == "line") dist = textHeight(); + var target = coordsChar(pos.x, pos.y + dist * dir + 2); + if (unit == "page") scrollbar.scrollTop += localCoords(target, true).y - pos.y; + setCursor(target.line, target.ch, true); + goalColumn = pos.x; + } + function selectWordAt(pos) { - var line = lines[pos.line].text; + var line = getLine(pos.line).text; var start = pos.ch, end = pos.ch; - while (start > 0 && /\w/.test(line.charAt(start - 1))) --start; - while (end < line.length && /\w/.test(line.charAt(end))) ++end; + while (start > 0 && isWordChar(line.charAt(start - 1))) --start; + while (end < line.length && isWordChar(line.charAt(end))) ++end; setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end}); } function selectLine(line) { - setSelectionUser({line: line, ch: 0}, {line: line, ch: lines[line].text.length}); - } - function handleEnter() { - replaceSelection("\n", "end"); - if (options.enterMode != "flat") - indentLine(sel.from.line, options.enterMode == "keep" ? "prev" : "smart"); + setSelectionUser({line: line, ch: 0}, clipPos({line: line + 1, ch: 0})); } - function handleTab(shift) { - function indentSelected(mode) { - if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); - var e = sel.to.line - (sel.to.ch ? 0 : 1); - for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); - } - shiftSelecting = null; - switch (options.tabMode) { - case "default": - return false; - case "indent": - indentSelected("smart"); - break; - case "classic": - if (posEq(sel.from, sel.to)) { - if (shift) indentLine(sel.from.line, "smart"); - else replaceSelection("\t", "end"); - break; - } - case "shift": - indentSelected(shift ? "subtract" : "add"); - break; - } - return true; - } - function smartHome() { - var firstNonWS = Math.max(0, lines[sel.from.line].text.search(/\S/)); - setCursor(sel.from.line, sel.from.ch <= firstNonWS && sel.from.ch ? 0 : firstNonWS, true); + function indentSelected(mode) { + if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode); + var e = sel.to.line - (sel.to.ch ? 0 : 1); + for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode); } function indentLine(n, how) { + if (!how) how = "add"; if (how == "smart") { if (!mode.indent) how = "prev"; else var state = getStateBefore(n); } - var line = lines[n], curSpace = line.indentation(), curSpaceString = line.text.match(/^\s*/)[0], indentation; + var line = getLine(n), curSpace = line.indentation(options.tabSize), + curSpaceString = line.text.match(/^\s*/)[0], indentation; + if (how == "smart") { + indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text); + if (indentation == Pass) how = "prev"; + } if (how == "prev") { - if (n) indentation = lines[n-1].indentation(); + if (n) indentation = getLine(n-1).indentation(options.tabSize); else indentation = 0; } - else if (how == "smart") indentation = mode.indent(state, line.text.slice(curSpaceString.length)); else if (how == "add") indentation = curSpace + options.indentUnit; else if (how == "subtract") indentation = curSpace - options.indentUnit; indentation = Math.max(0, indentation); @@ -1117,11 +1434,10 @@ var CodeMirror = (function() { if (!diff) { if (sel.from.line != n && sel.to.line != n) return; var indentString = curSpaceString; - } - else { + } else { var indentString = "", pos = 0; if (options.indentWithTabs) - for (var i = Math.floor(indentation / tabSize); i; --i) {pos += tabSize; indentString += "\t";} + for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += "\t";} while (pos < indentation) {++pos; indentString += " ";} } @@ -1130,8 +1446,7 @@ var CodeMirror = (function() { function loadMode() { mode = CodeMirror.getMode(options, options.mode); - for (var i = 0, l = lines.length; i < l; ++i) - lines[i].stateAfter = null; + doc.iter(0, doc.size, function(line) { line.stateAfter = null; }); work = [0]; startWorker(); } @@ -1141,35 +1456,56 @@ var CodeMirror = (function() { if (visible) gutterDirty = true; else lineDiv.parentNode.style.marginLeft = 0; } - - function markText(from, to, className) { - from = clipPos(from); to = clipPos(to); - var set = []; - function add(line, from, to, className) { - mark = lines[line].addMark(from, to, className, set); + function wrappingChanged(from, to) { + if (options.lineWrapping) { + wrapper.className += " CodeMirror-wrap"; + var perLine = scroller.clientWidth / charWidth() - 3; + doc.iter(0, doc.size, function(line) { + if (line.hidden) return; + var guess = Math.ceil(line.text.length / perLine) || 1; + if (guess != 1) updateLineHeight(line, guess); + }); + lineSpace.style.width = code.style.width = ""; + widthForcer.style.left = ""; + } else { + wrapper.className = wrapper.className.replace(" CodeMirror-wrap", ""); + maxLine = ""; maxLineChanged = true; + doc.iter(0, doc.size, function(line) { + if (line.height != 1 && !line.hidden) updateLineHeight(line, 1); + if (line.text.length > maxLine.length) maxLine = line.text; + }); } - if (from.line == to.line) add(from.line, from.ch, to.ch, className); - else { - add(from.line, from.ch, null, className); - for (var i = from.line + 1, e = to.line; i < e; ++i) - add(i, 0, null, className); - add(to.line, 0, to.ch, className); - } - changes.push({from: from.line, to: to.line + 1}); - return new TextMarker(set); + changes.push({from: 0, to: doc.size}); + } + function makeTab(col) { + var w = options.tabSize - col % options.tabSize, cached = tabCache[w]; + if (cached) return cached; + for (var str = '', i = 0; i < w; ++i) str += " "; + return (tabCache[w] = {html: str + "", width: w}); + } + function themeChanged() { + scroller.className = scroller.className.replace(/\s*cm-s-\S+/g, "") + + options.theme.replace(/(^|\s)\s*/g, " cm-s-"); + } + function keyMapChanged() { + var style = keyMap[options.keyMap].style; + wrapper.className = wrapper.className.replace(/\s*cm-keymap-\S+/g, "") + + (style ? " cm-keymap-" + style : ""); } - function TextMarker(set) { this.set = set; } + function TextMarker() { this.set = []; } TextMarker.prototype.clear = operation(function() { + var min = Infinity, max = -Infinity; for (var i = 0, e = this.set.length; i < e; ++i) { - var mk = this.set[i].marked; - for (var j = 0; j < mk.length; ++j) { - if (mk[j].set == this.set) mk.splice(j--, 1); - } + var line = this.set[i], mk = line.marked; + if (!mk || !line.parent) continue; + var lineN = lineNo(line); + min = Math.min(min, lineN); max = Math.max(max, lineN); + for (var j = 0; j < mk.length; ++j) + if (mk[j].marker == this) mk.splice(j--, 1); } - // We don't know the exact lines that changed. Refreshing is - // cheaper than finding them. - changes.push({from: 0, to: lines.length}); + if (min != Infinity) + changes.push({from: min, to: max + 1}); }); TextMarker.prototype.find = function() { var from, to; @@ -1177,10 +1513,10 @@ var CodeMirror = (function() { var line = this.set[i], mk = line.marked; for (var j = 0; j < mk.length; ++j) { var mark = mk[j]; - if (mark.set == this.set) { + if (mark.marker == this) { if (mark.from != null || mark.to != null) { - var found = indexOf(lines, line); - if (found > -1) { + var found = lineNo(line); + if (found != null) { if (mark.from != null) from = {line: found, ch: mark.from}; if (mark.to != null) to = {line: found, ch: mark.to}; } @@ -1191,45 +1527,113 @@ var CodeMirror = (function() { return {from: from, to: to}; }; + function markText(from, to, className) { + from = clipPos(from); to = clipPos(to); + var tm = new TextMarker(); + if (!posLess(from, to)) return tm; + function add(line, from, to, className) { + getLine(line).addMark(new MarkedText(from, to, className, tm)); + } + if (from.line == to.line) add(from.line, from.ch, to.ch, className); + else { + add(from.line, from.ch, null, className); + for (var i = from.line + 1, e = to.line; i < e; ++i) + add(i, null, null, className); + add(to.line, null, to.ch, className); + } + changes.push({from: from.line, to: to.line + 1}); + return tm; + } + + function setBookmark(pos) { + pos = clipPos(pos); + var bm = new Bookmark(pos.ch); + getLine(pos.line).addMark(bm); + return bm; + } + + function findMarksAt(pos) { + pos = clipPos(pos); + var markers = [], marked = getLine(pos.line).marked; + if (!marked) return markers; + for (var i = 0, e = marked.length; i < e; ++i) { + var m = marked[i]; + if ((m.from == null || m.from <= pos.ch) && + (m.to == null || m.to >= pos.ch)) + markers.push(m.marker || m); + } + return markers; + } + function addGutterMarker(line, text, className) { - if (typeof line == "number") line = lines[clipLine(line)]; + if (typeof line == "number") line = getLine(clipLine(line)); line.gutterMarker = {text: text, style: className}; gutterDirty = true; return line; } function removeGutterMarker(line) { - if (typeof line == "number") line = lines[clipLine(line)]; + if (typeof line == "number") line = getLine(clipLine(line)); line.gutterMarker = null; gutterDirty = true; } - function setLineClass(line, className) { - if (typeof line == "number") { - var no = line; - line = lines[clipLine(line)]; - } - else { - var no = indexOf(lines, line); - if (no == -1) return null; - } - if (line.className != className) { - line.className = className; - changes.push({from: no, to: no + 1}); - } + + function changeLine(handle, op) { + var no = handle, line = handle; + if (typeof handle == "number") line = getLine(clipLine(handle)); + else no = lineNo(handle); + if (no == null) return null; + if (op(line, no)) changes.push({from: no, to: no + 1}); + else return null; return line; } + function setLineClass(handle, className, bgClassName) { + return changeLine(handle, function(line) { + if (line.className != className || line.bgClassName != bgClassName) { + line.className = className; + line.bgClassName = bgClassName; + return true; + } + }); + } + function setLineHidden(handle, hidden) { + return changeLine(handle, function(line, no) { + if (line.hidden != hidden) { + line.hidden = hidden; + if (!options.lineWrapping) { + var l = line.text; + if (hidden && l.length == maxLine.length) { + updateMaxLine = true; + } else if (!hidden && l.length > maxLine.length) { + maxLine = l; maxWidth = null; updateMaxLine = false; + } + } + updateLineHeight(line, hidden ? 0 : 1); + var fline = sel.from.line, tline = sel.to.line; + if (hidden && (fline == no || tline == no)) { + var from = fline == no ? skipHidden({line: fline, ch: 0}, fline, 0) : sel.from; + var to = tline == no ? skipHidden({line: tline, ch: 0}, tline, 0) : sel.to; + // Can't hide the last visible line, we'd have no place to put the cursor + if (!to) return; + setSelection(from, to); + } + return (gutterDirty = true); + } + }); + } function lineInfo(line) { if (typeof line == "number") { + if (!isLine(line)) return null; var n = line; - line = lines[line]; + line = getLine(line); if (!line) return null; - } - else { - var n = indexOf(lines, line); - if (n == -1) return null; + } else { + var n = lineNo(line); + if (n == null) return null; } var marker = line.gutterMarker; - return {line: n, text: line.text, markerText: marker && marker.text, markerClass: marker && marker.style}; + return {line: n, handle: line, text: line.text, markerText: marker && marker.text, + markerClass: marker && marker.style, lineClass: line.className, bgClass: line.bgClassName}; } function stringWidth(str) { @@ -1239,21 +1643,15 @@ var CodeMirror = (function() { } // These are used to go from pixel positions to character // positions, taking varying character widths into account. - function charX(line, pos) { - if (pos == 0) return 0; - measure.innerHTML = "
" + lines[line].getHTML(null, null, false, pos) + "
"; - return measure.firstChild.firstChild.offsetWidth; - } function charFromX(line, x) { if (x <= 0) return 0; - var lineObj = lines[line], text = lineObj.text; + var lineObj = getLine(line), text = lineObj.text; function getX(len) { - measure.innerHTML = "
" + lineObj.getHTML(null, null, false, len) + "
"; - return measure.firstChild.firstChild.offsetWidth; + return measureLine(lineObj, len).left; } var from = 0, fromX = 0, to = text.length, toX; // Guess a suitable upper bound for our search. - var estimated = Math.min(to, Math.ceil(x / stringWidth("x"))); + var estimated = Math.min(to, Math.ceil(x / charWidth())); for (;;) { var estX = getX(estimated); if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); @@ -1272,20 +1670,95 @@ var CodeMirror = (function() { } } + var tempId = "CodeMirror-temp-" + Math.floor(Math.random() * 0xffffff).toString(16); + function measureLine(line, ch) { + if (ch == 0) return {top: 0, left: 0}; + var wbr = options.lineWrapping && ch < line.text.length && + spanAffectsWrapping.test(line.text.slice(ch - 1, ch + 1)); + measure.innerHTML = "
" + line.getHTML(makeTab, ch, tempId, wbr) + "
"; + var elt = document.getElementById(tempId); + var top = elt.offsetTop, left = elt.offsetLeft; + // Older IEs report zero offsets for spans directly after a wrap + if (ie && top == 0 && left == 0) { + var backup = document.createElement("span"); + backup.innerHTML = "x"; + elt.parentNode.insertBefore(backup, elt.nextSibling); + top = backup.offsetTop; + } + return {top: top, left: left}; + } function localCoords(pos, inLineWrap) { - var lh = lineHeight(), line = pos.line - (inLineWrap ? showingFrom : 0); - return {x: charX(pos.line, pos.ch), y: line * lh, yBot: (line + 1) * lh}; + var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0)); + if (pos.ch == 0) x = 0; + else { + var sp = measureLine(getLine(pos.line), pos.ch); + x = sp.left; + if (options.lineWrapping) y += Math.max(0, sp.top); + } + return {x: x, y: y, yBot: y + lh}; + } + // Coords must be lineSpace-local + function coordsChar(x, y) { + if (y < 0) y = 0; + var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th); + var lineNo = lineAtHeight(doc, heightPos); + if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length}; + var lineObj = getLine(lineNo), text = lineObj.text; + var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0; + if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0}; + function getX(len) { + var sp = measureLine(lineObj, len); + if (tw) { + var off = Math.round(sp.top / th); + return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth); + } + return sp.left; + } + var from = 0, fromX = 0, to = text.length, toX; + // Guess a suitable upper bound for our search. + var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw)); + for (;;) { + var estX = getX(estimated); + if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2)); + else {toX = estX; to = estimated; break;} + } + if (x > toX) return {line: lineNo, ch: to}; + // Try to guess a suitable lower bound as well. + estimated = Math.floor(to * 0.8); estX = getX(estimated); + if (estX < x) {from = estimated; fromX = estX;} + // Do a binary search between these bounds. + for (;;) { + if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to}; + var middle = Math.ceil((from + to) / 2), middleX = getX(middle); + if (middleX > x) {to = middle; toX = middleX;} + else {from = middle; fromX = middleX;} + } } function pageCoords(pos) { var local = localCoords(pos, true), off = eltOffset(lineSpace); return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot}; } - function lineHeight() { - var nlines = lineDiv.childNodes.length; - if (nlines) return (lineDiv.offsetHeight / nlines) || 1; - measure.innerHTML = "
x
"; - return measure.firstChild.offsetHeight || 1; + var cachedHeight, cachedHeightFor, measureText; + function textHeight() { + if (measureText == null) { + measureText = "
";
+        for (var i = 0; i < 49; ++i) measureText += "x
"; + measureText += "x
"; + } + var offsetHeight = lineDiv.clientHeight; + if (offsetHeight == cachedHeightFor) return cachedHeight; + cachedHeightFor = offsetHeight; + measure.innerHTML = measureText; + cachedHeight = measure.firstChild.offsetHeight / 50 || 1; + measure.innerHTML = ""; + return cachedHeight; + } + var cachedWidth, cachedWidthFor = 0; + function charWidth() { + if (scroller.clientWidth == cachedWidthFor) return cachedWidth; + cachedWidthFor = scroller.clientWidth; + return (cachedWidth = stringWidth("x")); } function paddingTop() {return lineSpace.offsetTop;} function paddingLeft() {return lineSpace.offsetLeft;} @@ -1300,12 +1773,11 @@ var CodeMirror = (function() { if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight)) return null; var offL = eltOffset(lineSpace, true); - var line = showingFrom + Math.floor((y - offL.top) / lineHeight()); - return clipPos({line: line, ch: charFromX(clipLine(line), x - offL.left)}); + return coordsChar(x - offL.left, y - offL.top); } function onContextMenu(e) { - var pos = posFromMouse(e); - if (!pos || window.opera) return; // Opera is difficult. + var pos = posFromMouse(e), scrollPos = scrollbar.scrollTop; + if (!pos || opera) return; // Opera is difficult. if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to)) operation(setCursor)(pos.line, pos.ch); @@ -1317,14 +1789,15 @@ var CodeMirror = (function() { leaveInputAlone = true; var val = input.value = getSelection(); focusInput(); - setSelRange(input, 0, input.value.length); + selectInput(input); function rehide() { var newVal = splitLines(input.value).join("\n"); - if (newVal != val) operation(replaceSelection)(newVal, "end"); + if (newVal != val && !options.readOnly) operation(replaceSelection)(newVal, "end"); inputDiv.style.position = "relative"; input.style.cssText = oldCSS; + if (ie_lt9) scrollbar.scrollTop = scrollPos; leaveInputAlone = false; - prepareInput(); + resetInput(true); slowPoll(); } @@ -1334,8 +1807,7 @@ var CodeMirror = (function() { mouseup(); setTimeout(rehide, 20); }, true); - } - else { + } else { setTimeout(rehide, 50); } } @@ -1352,7 +1824,7 @@ var CodeMirror = (function() { var matching = {"(": ")>", ")": "(<", "[": "]>", "]": "[<", "{": "}>", "}": "{<"}; function matchBrackets(autoclear) { - var head = sel.inverted ? sel.from : sel.to, line = lines[head.line], pos = head.ch - 1; + var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1; var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)]; if (!match) return; var ch = match.charAt(0), forward = match.charAt(1) == ">", d = forward ? 1 : -1, st = line.styles; @@ -1365,7 +1837,7 @@ var CodeMirror = (function() { var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur; for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) { var text = st[i]; - if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;} + if (st[i+1] != style) {pos += d * text.length; continue;} for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) { if (pos >= from && pos < to && re.test(cur = text.charAt(j))) { var match = matching[cur]; @@ -1376,8 +1848,8 @@ var CodeMirror = (function() { } } } - for (var i = head.line, e = forward ? Math.min(i + 100, lines.length) : Math.max(-1, i - 100); i != e; i+=d) { - var line = lines[i], first = i == head.line; + for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) { + var line = getLine(i), first = i == head.line; var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length); if (found) break; } @@ -1399,9 +1871,9 @@ var CodeMirror = (function() { var minindent, minline; for (var search = n, lim = n - 40; search > lim; --search) { if (search == 0) return 0; - var line = lines[search-1]; + var line = getLine(search-1); if (line.stateAfter) return search; - var indented = line.indentation(); + var indented = line.indentation(options.tabSize); if (minline == null || minindent > indented) { minline = search - 1; minindent = indented; @@ -1410,56 +1882,62 @@ var CodeMirror = (function() { return minline; } function getStateBefore(n) { - var start = findStartLine(n), state = start && lines[start-1].stateAfter; + var start = findStartLine(n), state = start && getLine(start-1).stateAfter; if (!state) state = startState(mode); else state = copyState(mode, state); - for (var i = start; i < n; ++i) { - var line = lines[i]; - line.highlight(mode, state); + doc.iter(start, n, function(line) { + line.highlight(mode, state, options.tabSize); line.stateAfter = copyState(mode, state); - } - changes.push({from: start, to: n}); - if (n < lines.length && !lines[n].stateAfter) work.push(n); + }); + if (start < n) changes.push({from: start, to: n}); + if (n < doc.size && !getLine(n).stateAfter) work.push(n); return state; } function highlightLines(start, end) { var state = getStateBefore(start); - for (var i = start; i < end; ++i) { - var line = lines[i]; - line.highlight(mode, state); + doc.iter(start, end, function(line) { + line.highlight(mode, state, options.tabSize); line.stateAfter = copyState(mode, state); - } + }); } function highlightWorker() { var end = +new Date + options.workTime; var foundWork = work.length; while (work.length) { - if (!lines[showingFrom].stateAfter) var task = showingFrom; + if (!getLine(showingFrom).stateAfter) var task = showingFrom; else var task = work.pop(); - if (task >= lines.length) continue; - var start = findStartLine(task), state = start && lines[start-1].stateAfter; + if (task >= doc.size) continue; + var start = findStartLine(task), state = start && getLine(start-1).stateAfter; if (state) state = copyState(mode, state); else state = startState(mode); - var unchanged = 0, compare = mode.compareStates, realChange = false; - for (var i = start, l = lines.length; i < l; ++i) { - var line = lines[i], hadState = line.stateAfter; + var unchanged = 0, compare = mode.compareStates, realChange = false, + i = start, bail = false; + doc.iter(i, doc.size, function(line) { + var hadState = line.stateAfter; if (+new Date > end) { work.push(i); startWorker(options.workDelay); if (realChange) changes.push({from: task, to: i + 1}); - return; + return (bail = true); } - var changed = line.highlight(mode, state); + var changed = line.highlight(mode, state, options.tabSize); if (changed) realChange = true; line.stateAfter = copyState(mode, state); + var done = null; if (compare) { - if (hadState && compare(hadState, state)) break; - } else { + var same = hadState && compare(hadState, state); + if (same != Pass) done = !!same; + } + if (done == null) { if (changed !== false || !hadState) unchanged = 0; - else if (++unchanged > 3) break; + else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, "") == mode.indent(state, ""))) + done = true; } - } + if (done) return true; + ++i; + }); + if (bail) return; if (realChange) changes.push({from: task, to: i + 1}); } if (foundWork && options.onHighlightComplete) @@ -1475,35 +1953,44 @@ var CodeMirror = (function() { // be awkward, slow, and error-prone), but instead updates are // batched and then all combined and executed at once. function startOperation() { - updateInput = null; changes = []; textChanged = selectionChanged = false; + updateInput = userSelChange = textChanged = null; + changes = []; selectionChanged = false; callbacks = []; } function endOperation() { - var reScroll = false; - if (selectionChanged) reScroll = !scrollCursorIntoView(); - if (changes.length) updateDisplay(changes); + if (updateMaxLine) computeMaxLength(); + if (maxLineChanged && !options.lineWrapping) { + widthForcer.style.left = stringWidth(maxLine) + "px"; + maxLineChanged = false; + } + var newScrollPos, updated; + if (selectionChanged) { + var coords = calculateCursorCoords(); + newScrollPos = calculateScrollPos(coords.x, coords.y, coords.x, coords.yBot); + } + if (changes.length) updated = updateDisplay(changes, true, (newScrollPos ? newScrollPos.scrollTop : null)); else { - if (selectionChanged) updateCursor(); + if (selectionChanged) updateSelection(); if (gutterDirty) updateGutter(); } - if (reScroll) scrollCursorIntoView(); + if (newScrollPos) scrollCursorIntoView(); if (selectionChanged) {scrollEditorIntoView(); restartBlink();} - // updateInput can be set to a boolean value to force/prevent an - // update. if (focused && !leaveInputAlone && (updateInput === true || (updateInput !== false && selectionChanged))) - prepareInput(); + resetInput(userSelChange); if (selectionChanged && options.matchBrackets) setTimeout(operation(function() { if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;} - matchBrackets(false); + if (posEq(sel.from, sel.to)) matchBrackets(false); }), 20); - var tc = textChanged; // textChanged can be reset by cursoractivity callback - if (selectionChanged && options.onCursorActivity) + var sc = selectionChanged, cbs = callbacks; // these can be reset by callbacks + if (textChanged && options.onChange && instance) + options.onChange(instance, textChanged); + if (sc && options.onCursorActivity) options.onCursorActivity(instance); - if (tc && options.onChange && instance) - options.onChange(instance, tc); + for (var i = 0; i < cbs.length; ++i) cbs[i](instance); + if (updated && options.onUpdate) options.onUpdate(instance); } var nestedOperation = 0; function operation(f) { @@ -1515,120 +2002,11 @@ var CodeMirror = (function() { }; } - function SearchCursor(query, pos, caseFold) { - this.atOccurrence = false; - if (caseFold == null) caseFold = typeof query == "string" && query == query.toLowerCase(); - - if (pos && typeof pos == "object") pos = clipPos(pos); - else pos = {line: 0, ch: 0}; - this.pos = {from: pos, to: pos}; - - // The matches method is filled in based on the type of query. - // It takes a position and a direction, and returns an object - // describing the next occurrence of the query, or null if no - // more matches were found. - if (typeof query != "string") // Regexp match - this.matches = function(reverse, pos) { - if (reverse) { - var line = lines[pos.line].text.slice(0, pos.ch), match = line.match(query), start = 0; - while (match) { - var ind = line.indexOf(match[0]); - start += ind; - line = line.slice(ind + 1); - var newmatch = line.match(query); - if (newmatch) match = newmatch; - else break; - start++; - } - } - else { - var line = lines[pos.line].text.slice(pos.ch), match = line.match(query), - start = match && pos.ch + line.indexOf(match[0]); - } - if (match) - return {from: {line: pos.line, ch: start}, - to: {line: pos.line, ch: start + match[0].length}, - match: match}; - }; - else { // String query - if (caseFold) query = query.toLowerCase(); - var fold = caseFold ? function(str){return str.toLowerCase();} : function(str){return str;}; - var target = query.split("\n"); - // Different methods for single-line and multi-line queries - if (target.length == 1) - this.matches = function(reverse, pos) { - var line = fold(lines[pos.line].text), len = query.length, match; - if (reverse ? (pos.ch >= len && (match = line.lastIndexOf(query, pos.ch - len)) != -1) - : (match = line.indexOf(query, pos.ch)) != -1) - return {from: {line: pos.line, ch: match}, - to: {line: pos.line, ch: match + len}}; - }; - else - this.matches = function(reverse, pos) { - var ln = pos.line, idx = (reverse ? target.length - 1 : 0), match = target[idx], line = fold(lines[ln].text); - var offsetA = (reverse ? line.indexOf(match) + match.length : line.lastIndexOf(match)); - if (reverse ? offsetA >= pos.ch || offsetA != match.length - : offsetA <= pos.ch || offsetA != line.length - match.length) - return; - for (;;) { - if (reverse ? !ln : ln == lines.length - 1) return; - line = fold(lines[ln += reverse ? -1 : 1].text); - match = target[reverse ? --idx : ++idx]; - if (idx > 0 && idx < target.length - 1) { - if (line != match) return; - else continue; - } - var offsetB = (reverse ? line.lastIndexOf(match) : line.indexOf(match) + match.length); - if (reverse ? offsetB != line.length - match.length : offsetB != match.length) - return; - var start = {line: pos.line, ch: offsetA}, end = {line: ln, ch: offsetB}; - return {from: reverse ? end : start, to: reverse ? start : end}; - } - }; - } + function compoundChange(f) { + history.startCompound(); + try { return f(); } finally { history.endCompound(); } } - SearchCursor.prototype = { - findNext: function() {return this.find(false);}, - findPrevious: function() {return this.find(true);}, - - find: function(reverse) { - var self = this, pos = clipPos(reverse ? this.pos.from : this.pos.to); - function savePosAndFail(line) { - var pos = {line: line, ch: 0}; - self.pos = {from: pos, to: pos}; - self.atOccurrence = false; - return false; - } - - for (;;) { - if (this.pos = this.matches(reverse, pos)) { - this.atOccurrence = true; - return this.pos.match || true; - } - if (reverse) { - if (!pos.line) return savePosAndFail(0); - pos = {line: pos.line-1, ch: lines[pos.line-1].text.length}; - } - else { - if (pos.line == lines.length - 1) return savePosAndFail(lines.length); - pos = {line: pos.line+1, ch: 0}; - } - } - }, - - from: function() {if (this.atOccurrence) return copyPos(this.pos.from);}, - to: function() {if (this.atOccurrence) return copyPos(this.pos.to);}, - - replace: function(newText) { - var self = this; - if (this.atOccurrence) - operation(function() { - self.pos.to = replaceRange(newText, self.pos.from, self.pos.to); - })(); - } - }; - for (var ext in extensions) if (extensions.propertyIsEnumerable(ext) && !instance.propertyIsEnumerable(ext)) @@ -1643,51 +2021,66 @@ var CodeMirror = (function() { theme: "default", indentUnit: 2, indentWithTabs: false, - tabMode: "classic", - enterMode: "indent", + smartIndent: true, + tabSize: 4, + keyMap: "default", + extraKeys: null, electricChars: true, + autoClearEmptyLines: false, onKeyEvent: null, + onDragEvent: null, + lineWrapping: false, lineNumbers: false, gutter: false, fixedGutter: false, firstLineNumber: 1, readOnly: false, - smartHome: true, + dragDrop: true, onChange: null, onCursorActivity: null, onGutterClick: null, onHighlightComplete: null, + onUpdate: null, onFocus: null, onBlur: null, onScroll: null, matchBrackets: false, workTime: 100, workDelay: 200, + pollInterval: 100, undoDepth: 40, tabindex: null, - document: window.document + autofocus: null }; + var ios = /AppleWebKit/.test(navigator.userAgent) && /Mobile\/\w+/.test(navigator.userAgent); + var mac = ios || /Mac/.test(navigator.platform); + var win = /Win/.test(navigator.platform); + // Known modes, by name and by MIME - var modes = {}, mimeModes = {}; + var modes = CodeMirror.modes = {}, mimeModes = CodeMirror.mimeModes = {}; CodeMirror.defineMode = function(name, mode) { if (!CodeMirror.defaults.mode && name != "null") CodeMirror.defaults.mode = name; + if (arguments.length > 2) { + mode.dependencies = []; + for (var i = 2; i < arguments.length; ++i) mode.dependencies.push(arguments[i]); + } modes[name] = mode; }; CodeMirror.defineMIME = function(mime, spec) { mimeModes[mime] = spec; }; - CodeMirror.getMode = function(options, spec) { + CodeMirror.resolveMode = function(spec) { if (typeof spec == "string" && mimeModes.hasOwnProperty(spec)) spec = mimeModes[spec]; - if (typeof spec == "string") - var mname = spec, config = {}; - else if (spec != null) - var mname = spec.name, config = spec; - var mfactory = modes[mname]; - if (!mfactory) { - if (window.console) console.warn("No mode " + mname + " found, falling back to plain text."); - return CodeMirror.getMode(options, "text/plain"); - } - return mfactory(options, config || {}); + else if (typeof spec == "string" && /^[\w\-]+\/[\w\-]+\+xml$/.test(spec)) + return CodeMirror.resolveMode("application/xml"); + if (typeof spec == "string") return {name: spec}; + else return spec || {name: "null"}; + }; + CodeMirror.getMode = function(options, spec) { + var spec = CodeMirror.resolveMode(spec); + var mfactory = modes[spec.name]; + if (!mfactory) return CodeMirror.getMode(options, "text/plain"); + return mfactory(options, spec); }; CodeMirror.listModes = function() { var list = []; @@ -1702,16 +2095,137 @@ var CodeMirror = (function() { return list; }; - var extensions = {}; + var extensions = CodeMirror.extensions = {}; CodeMirror.defineExtension = function(name, func) { extensions[name] = func; }; + var commands = CodeMirror.commands = { + selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});}, + killLine: function(cm) { + var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); + if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange("", from, {line: from.line + 1, ch: 0}); + else cm.replaceRange("", from, sel ? to : {line: from.line}); + }, + deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange("", {line: l, ch: 0}, {line: l});}, + undo: function(cm) {cm.undo();}, + redo: function(cm) {cm.redo();}, + goDocStart: function(cm) {cm.setCursor(0, 0, true);}, + goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);}, + goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);}, + goLineStartSmart: function(cm) { + var cur = cm.getCursor(); + var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\S/)); + cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true); + }, + goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);}, + goLineUp: function(cm) {cm.moveV(-1, "line");}, + goLineDown: function(cm) {cm.moveV(1, "line");}, + goPageUp: function(cm) {cm.moveV(-1, "page");}, + goPageDown: function(cm) {cm.moveV(1, "page");}, + goCharLeft: function(cm) {cm.moveH(-1, "char");}, + goCharRight: function(cm) {cm.moveH(1, "char");}, + goColumnLeft: function(cm) {cm.moveH(-1, "column");}, + goColumnRight: function(cm) {cm.moveH(1, "column");}, + goWordLeft: function(cm) {cm.moveH(-1, "word");}, + goWordRight: function(cm) {cm.moveH(1, "word");}, + delCharLeft: function(cm) {cm.deleteH(-1, "char");}, + delCharRight: function(cm) {cm.deleteH(1, "char");}, + delWordLeft: function(cm) {cm.deleteH(-1, "word");}, + delWordRight: function(cm) {cm.deleteH(1, "word");}, + indentAuto: function(cm) {cm.indentSelection("smart");}, + indentMore: function(cm) {cm.indentSelection("add");}, + indentLess: function(cm) {cm.indentSelection("subtract");}, + insertTab: function(cm) {cm.replaceSelection("\t", "end");}, + defaultTab: function(cm) { + if (cm.somethingSelected()) cm.indentSelection("add"); + else cm.replaceSelection("\t", "end"); + }, + transposeChars: function(cm) { + var cur = cm.getCursor(), line = cm.getLine(cur.line); + if (cur.ch > 0 && cur.ch < line.length - 1) + cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1), + {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1}); + }, + newlineAndIndent: function(cm) { + cm.replaceSelection("\n", "end"); + cm.indentLine(cm.getCursor().line); + }, + toggleOverwrite: function(cm) {cm.toggleOverwrite();} + }; + + var keyMap = CodeMirror.keyMap = {}; + keyMap.basic = { + "Left": "goCharLeft", "Right": "goCharRight", "Up": "goLineUp", "Down": "goLineDown", + "End": "goLineEnd", "Home": "goLineStartSmart", "PageUp": "goPageUp", "PageDown": "goPageDown", + "Delete": "delCharRight", "Backspace": "delCharLeft", "Tab": "defaultTab", "Shift-Tab": "indentAuto", + "Enter": "newlineAndIndent", "Insert": "toggleOverwrite" + }; + // Note that the save and find-related commands aren't defined by + // default. Unknown commands are simply ignored. + keyMap.pcDefault = { + "Ctrl-A": "selectAll", "Ctrl-D": "deleteLine", "Ctrl-Z": "undo", "Shift-Ctrl-Z": "redo", "Ctrl-Y": "redo", + "Ctrl-Home": "goDocStart", "Alt-Up": "goDocStart", "Ctrl-End": "goDocEnd", "Ctrl-Down": "goDocEnd", + "Ctrl-Left": "goWordLeft", "Ctrl-Right": "goWordRight", "Alt-Left": "goLineStart", "Alt-Right": "goLineEnd", + "Ctrl-Backspace": "delWordLeft", "Ctrl-Delete": "delWordRight", "Ctrl-S": "save", "Ctrl-F": "find", + "Ctrl-G": "findNext", "Shift-Ctrl-G": "findPrev", "Shift-Ctrl-F": "replace", "Shift-Ctrl-R": "replaceAll", + "Ctrl-[": "indentLess", "Ctrl-]": "indentMore", + fallthrough: "basic" + }; + keyMap.macDefault = { + "Cmd-A": "selectAll", "Cmd-D": "deleteLine", "Cmd-Z": "undo", "Shift-Cmd-Z": "redo", "Cmd-Y": "redo", + "Cmd-Up": "goDocStart", "Cmd-End": "goDocEnd", "Cmd-Down": "goDocEnd", "Alt-Left": "goWordLeft", + "Alt-Right": "goWordRight", "Cmd-Left": "goLineStart", "Cmd-Right": "goLineEnd", "Alt-Backspace": "delWordLeft", + "Ctrl-Alt-Backspace": "delWordRight", "Alt-Delete": "delWordRight", "Cmd-S": "save", "Cmd-F": "find", + "Cmd-G": "findNext", "Shift-Cmd-G": "findPrev", "Cmd-Alt-F": "replace", "Shift-Cmd-Alt-F": "replaceAll", + "Cmd-[": "indentLess", "Cmd-]": "indentMore", + fallthrough: ["basic", "emacsy"] + }; + keyMap["default"] = mac ? keyMap.macDefault : keyMap.pcDefault; + keyMap.emacsy = { + "Ctrl-F": "goCharRight", "Ctrl-B": "goCharLeft", "Ctrl-P": "goLineUp", "Ctrl-N": "goLineDown", + "Alt-F": "goWordRight", "Alt-B": "goWordLeft", "Ctrl-A": "goLineStart", "Ctrl-E": "goLineEnd", + "Ctrl-V": "goPageUp", "Shift-Ctrl-V": "goPageDown", "Ctrl-D": "delCharRight", "Ctrl-H": "delCharLeft", + "Alt-D": "delWordRight", "Alt-Backspace": "delWordLeft", "Ctrl-K": "killLine", "Ctrl-T": "transposeChars" + }; + + function getKeyMap(val) { + if (typeof val == "string") return keyMap[val]; + else return val; + } + function lookupKey(name, extraMap, map, handle, stop) { + function lookup(map) { + map = getKeyMap(map); + var found = map[name]; + if (found != null && handle(found)) return true; + if (map.nofallthrough) { + if (stop) stop(); + return true; + } + var fallthrough = map.fallthrough; + if (fallthrough == null) return false; + if (Object.prototype.toString.call(fallthrough) != "[object Array]") + return lookup(fallthrough); + for (var i = 0, e = fallthrough.length; i < e; ++i) { + if (lookup(fallthrough[i])) return true; + } + return false; + } + if (extraMap && lookup(extraMap)) return true; + return lookup(map); + } + function isModifierKey(event) { + var name = keyNames[e_prop(event, "keyCode")]; + return name == "Ctrl" || name == "Alt" || name == "Shift" || name == "Mod"; + } + CodeMirror.fromTextArea = function(textarea, options) { if (!options) options = {}; options.value = textarea.value; if (!options.tabindex && textarea.tabindex) options.tabindex = textarea.tabindex; + if (options.autofocus == null && textarea.getAttribute("autofocus") != null) + options.autofocus = true; function save() {textarea.value = instance.getValue();} if (textarea.form) { @@ -1734,6 +2248,7 @@ var CodeMirror = (function() { textarea.parentNode.insertBefore(node, textarea.nextSibling); }, options); instance.save = save; + instance.getTextArea = function() { return textarea; }; instance.toTextArea = function() { save(); textarea.parentNode.removeChild(instance.getWrapperElement()); @@ -1760,16 +2275,17 @@ var CodeMirror = (function() { } return nstate; } - CodeMirror.startState = startState; + CodeMirror.copyState = copyState; function startState(mode, a1, a2) { return mode.startState ? mode.startState(a1, a2) : true; } - CodeMirror.copyState = copyState; + CodeMirror.startState = startState; // The character stream used by a mode's parser. - function StringStream(string) { + function StringStream(string, tabSize) { this.pos = this.start = 0; this.string = string; + this.tabSize = tabSize || 8; } StringStream.prototype = { eol: function() {return this.pos >= this.string.length;}, @@ -1801,8 +2317,8 @@ var CodeMirror = (function() { if (found > -1) {this.pos = found; return true;} }, backUp: function(n) {this.pos -= n;}, - column: function() {return countColumn(this.string, this.start);}, - indentation: function() {return countColumn(this.string);}, + column: function() {return countColumn(this.string, this.start, this.tabSize);}, + indentation: function() {return countColumn(this.string, null, this.tabSize);}, match: function(pattern, consume, caseInsensitive) { if (typeof pattern == "string") { function cased(str) {return caseInsensitive ? str.toLowerCase() : str;} @@ -1810,8 +2326,7 @@ var CodeMirror = (function() { if (consume !== false) this.pos += pattern.length; return true; } - } - else { + } else { var match = this.string.slice(this.pos).match(pattern); if (match && consume !== false) this.pos += match[0].length; return match; @@ -1821,22 +2336,86 @@ var CodeMirror = (function() { }; CodeMirror.StringStream = StringStream; + function MarkedText(from, to, className, marker) { + this.from = from; this.to = to; this.style = className; this.marker = marker; + } + MarkedText.prototype = { + attach: function(line) { this.marker.set.push(line); }, + detach: function(line) { + var ix = indexOf(this.marker.set, line); + if (ix > -1) this.marker.set.splice(ix, 1); + }, + split: function(pos, lenBefore) { + if (this.to <= pos && this.to != null) return null; + var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore; + var to = this.to == null ? null : this.to - pos + lenBefore; + return new MarkedText(from, to, this.style, this.marker); + }, + dup: function() { return new MarkedText(null, null, this.style, this.marker); }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if (fromOpen && to > this.from && (to < this.to || this.to == null)) + this.from = null; + else if (this.from != null && this.from >= from) + this.from = Math.max(to, this.from) + diff; + if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null)) + this.to = null; + else if (this.to != null && this.to > from) + this.to = to < this.to ? this.to + diff : from; + }, + isDead: function() { return this.from != null && this.to != null && this.from >= this.to; }, + sameSet: function(x) { return this.marker == x.marker; } + }; + + function Bookmark(pos) { + this.from = pos; this.to = pos; this.line = null; + } + Bookmark.prototype = { + attach: function(line) { this.line = line; }, + detach: function(line) { if (this.line == line) this.line = null; }, + split: function(pos, lenBefore) { + if (pos < this.from) { + this.from = this.to = (this.from - pos) + lenBefore; + return this; + } + }, + isDead: function() { return this.from > this.to; }, + clipTo: function(fromOpen, from, toOpen, to, diff) { + if ((fromOpen || from < this.from) && (toOpen || to > this.to)) { + this.from = 0; this.to = -1; + } else if (this.from > from) { + this.from = this.to = Math.max(to, this.from) + diff; + } + }, + sameSet: function(x) { return false; }, + find: function() { + if (!this.line || !this.line.parent) return null; + return {line: lineNo(this.line), ch: this.from}; + }, + clear: function() { + if (this.line) { + var found = indexOf(this.line.marked, this); + if (found != -1) this.line.marked.splice(found, 1); + this.line = null; + } + } + }; + // Line objects. These hold state related to a line, including // highlighting info (the styles array). function Line(text, styles) { this.styles = styles || [text, null]; - this.stateAfter = null; this.text = text; - this.marked = this.gutterMarker = this.className = null; + this.height = 1; + this.marked = this.gutterMarker = this.className = this.bgClassName = this.handlers = null; + this.stateAfter = this.parent = this.hidden = null; } Line.inheritMarks = function(text, orig) { - var ln = new Line(text), mk = orig.marked; + var ln = new Line(text), mk = orig && orig.marked; if (mk) { for (var i = 0; i < mk.length; ++i) { - if (mk[i].to == null) { + if (mk[i].to == null && mk[i].style) { var newmk = ln.marked || (ln.marked = []), mark = mk[i]; - newmk.push({from: null, to: null, style: mark.style, set: mark.set}); - mark.set.push(ln); + var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln); } } } @@ -1853,23 +2432,11 @@ var CodeMirror = (function() { this.text = this.text.slice(0, from) + text + this.text.slice(to); this.stateAfter = null; if (mk) { - var diff = text.length - (to - from), end = this.text.length; - var changeStart = Math.min(from, from + diff); + var diff = text.length - (to - from); for (var i = 0; i < mk.length; ++i) { - var mark = mk[i], del = false; - if (mark.from != null && mark.from >= end) del = true; - else { - if (mark.from != null && mark.from >= from) { - mark.from += diff; - if (mark.from <= 0) mark.from = from == null ? null : 0; - } - else if (to_ == null) mark.to = null; - if (mark.to != null && mark.to > from) { - mark.to += diff; - if (mark.to < 0) del = true; - } - } - if (del || (mark.from != null && mark.to != null && mark.from >= mark.to)) mk.splice(i--, 1); + var mark = mk[i]; + mark.clipTo(from == null, from || 0, to_ == null, to, diff); + if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);} } } }, @@ -1881,57 +2448,77 @@ var CodeMirror = (function() { if (mk) { for (var i = 0; i < mk.length; ++i) { var mark = mk[i]; - if (mark.to > pos || mark.to == null) { + var newmark = mark.split(pos, textBefore.length); + if (newmark) { if (!taken.marked) taken.marked = []; - taken.marked.push({ - from: mark.from < pos || mark.from == null ? null : mark.from - pos + textBefore.length, - to: mark.to == null ? null : mark.to - pos + textBefore.length, - style: mark.style, set: mark.set - }); - mark.set.push(taken); + taken.marked.push(newmark); newmark.attach(taken); + if (newmark == mark) mk.splice(i--, 1); } } } return taken; }, append: function(line) { - if (!line.text.length) return; - var mylen = this.text.length, mk = line.marked; + var mylen = this.text.length, mk = line.marked, mymk = this.marked; this.text += line.text; copyStyles(0, line.text.length, line.styles, this.styles); - if (mk && mk.length) { - var mymk = this.marked || (this.marked = []); + if (mymk) { for (var i = 0; i < mymk.length; ++i) if (mymk[i].to == null) mymk[i].to = mylen; + } + if (mk && mk.length) { + if (!mymk) this.marked = mymk = []; outer: for (var i = 0; i < mk.length; ++i) { var mark = mk[i]; if (!mark.from) { for (var j = 0; j < mymk.length; ++j) { var mymark = mymk[j]; - if (mymark.to == mylen && mymark.set == mark.set) { + if (mymark.to == mylen && mymark.sameSet(mark)) { mymark.to = mark.to == null ? null : mark.to + mylen; + if (mymark.isDead()) { + mymark.detach(this); + mk.splice(i--, 1); + } continue outer; } } } mymk.push(mark); - mark.set.push(this); + mark.attach(this); mark.from += mylen; if (mark.to != null) mark.to += mylen; } } }, - addMark: function(from, to, style, set) { - set.push(this); + fixMarkEnds: function(other) { + var mk = this.marked, omk = other.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) { + var mark = mk[i], close = mark.to == null; + if (close && omk) { + for (var j = 0; j < omk.length; ++j) + if (omk[j].sameSet(mark)) {close = false; break;} + } + if (close) mark.to = this.text.length; + } + }, + fixMarkStarts: function() { + var mk = this.marked; + if (!mk) return; + for (var i = 0; i < mk.length; ++i) + if (mk[i].from == null) mk[i].from = 0; + }, + addMark: function(mark) { + mark.attach(this); if (this.marked == null) this.marked = []; - this.marked.push({from: from, to: to, style: style, set: set}); + this.marked.push(mark); this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);}); }, // Run the given mode's parser over a line, update the styles // array, which contains alternating fragments of text and CSS // classes. - highlight: function(mode, state) { - var stream = new StringStream(this.text), st = this.styles, pos = 0; + highlight: function(mode, state, tabSize) { + var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0; var changed = false, curWord = st[0], prevWord; if (this.text == "" && mode.blankLine) mode.blankLine(state); while (!stream.eol()) { @@ -1972,74 +2559,125 @@ var CodeMirror = (function() { className: style || null, state: state}; }, - indentation: function() {return countColumn(this.text);}, + indentation: function(tabSize) {return countColumn(this.text, null, tabSize);}, // Produces an HTML fragment for the line, taking selection, // marking, and highlighting into account. - getHTML: function(sfrom, sto, includePre, endAt) { - var html = []; - if (includePre) - html.push(this.className ? '
': "
");
-      function span(text, style) {
+    getHTML: function(makeTab, wrapAt, wrapId, wrapWBR) {
+      var html = [], first = true, col = 0;
+      function span_(text, style) {
         if (!text) return;
-        if (style) html.push('', htmlEscape(text), "");
-        else html.push(htmlEscape(text));
+        // Work around a bug where, in some compat modes, IE ignores leading spaces
+        if (first && ie && text.charAt(0) == " ") text = "\u00a0" + text.slice(1);
+        first = false;
+        if (text.indexOf("\t") == -1) {
+          col += text.length;
+          var escaped = htmlEscape(text);
+        } else {
+          var escaped = "";
+          for (var pos = 0;;) {
+            var idx = text.indexOf("\t", pos);
+            if (idx == -1) {
+              escaped += htmlEscape(text.slice(pos));
+              col += text.length - pos;
+              break;
+            } else {
+              col += idx - pos;
+              var tab = makeTab(col);
+              escaped += htmlEscape(text.slice(pos, idx)) + tab.html;
+              col += tab.width;
+              pos = idx + 1;
+            }
+          }
+        }
+        if (style) html.push('', escaped, "");
+        else html.push(escaped);
       }
+      var span = span_;
+      if (wrapAt != null) {
+        var outPos = 0, open = "";
+        span = function(text, style) {
+          var l = text.length;
+          if (wrapAt >= outPos && wrapAt < outPos + l) {
+            if (wrapAt > outPos) {
+              span_(text.slice(0, wrapAt - outPos), style);
+              // See comment at the definition of spanAffectsWrapping
+              if (wrapWBR) html.push("");
+            }
+            html.push(open);
+            var cut = wrapAt - outPos;
+            span_(opera ? text.slice(cut, cut + 1) : text.slice(cut), style);
+            html.push("");
+            if (opera) span_(text.slice(cut + 1), style);
+            wrapAt--;
+            outPos += l;
+          } else {
+            outPos += l;
+            span_(text, style);
+            // Output empty wrapper when at end of line
+            if (outPos == wrapAt && outPos == len) html.push(open + " ");
+            // Stop outputting HTML when gone sufficiently far beyond measure
+            else if (outPos > wrapAt + 10 && /\s/.test(text)) span = function(){};
+          }
+        }
+      }
+
       var st = this.styles, allText = this.text, marked = this.marked;
-      if (sfrom == sto) sfrom = null;
       var len = allText.length;
-      if (endAt != null) len = Math.min(endAt, len);
+      function styleToClass(style) {
+        if (!style) return null;
+        return "cm-" + style.replace(/ +/g, " cm-");
+      }
 
-      if (!allText && endAt == null)
-        span(" ", sfrom != null && sto == null ? "CodeMirror-selected" : null);
-      else if (!marked && sfrom == null)
+      if (!allText && wrapAt == null) {
+        span(" ");
+      } else if (!marked || !marked.length) {
         for (var i = 0, ch = 0; ch < len; i+=2) {
           var str = st[i], style = st[i+1], l = str.length;
           if (ch + l > len) str = str.slice(0, len - ch);
           ch += l;
-          span(str, style && "cm-" + style);
+          span(str, styleToClass(style));
         }
-      else {
+      } else {
         var pos = 0, i = 0, text = "", style, sg = 0;
-        var markpos = -1, mark = null;
-        function nextMark() {
-          if (marked) {
-            markpos += 1;
-            mark = (markpos < marked.length) ? marked[markpos] : null;
+        var nextChange = marked[0].from || 0, marks = [], markpos = 0;
+        function advanceMarks() {
+          var m;
+          while (markpos < marked.length &&
+                 ((m = marked[markpos]).from == pos || m.from == null)) {
+            if (m.style != null) marks.push(m);
+            ++markpos;
+          }
+          nextChange = markpos < marked.length ? marked[markpos].from : Infinity;
+          for (var i = 0; i < marks.length; ++i) {
+            var to = marks[i].to || Infinity;
+            if (to == pos) marks.splice(i--, 1);
+            else nextChange = Math.min(to, nextChange);
           }
         }
-        nextMark();
+        var m = 0;
         while (pos < len) {
-          var upto = len;
-          var extraStyle = "";
-          if (sfrom != null) {
-            if (sfrom > pos) upto = sfrom;
-            else if (sto == null || sto > pos) {
-              extraStyle = " CodeMirror-selected";
-              if (sto != null) upto = Math.min(upto, sto);
+          if (nextChange == pos) advanceMarks();
+          var upto = Math.min(len, nextChange);
+          while (true) {
+            if (text) {
+              var end = pos + text.length;
+              var appliedStyle = style;
+              for (var j = 0; j < marks.length; ++j)
+                appliedStyle = (appliedStyle ? appliedStyle + " " : "") + marks[j].style;
+              span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
+              if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
+              pos = end;
             }
-          }
-          while (mark && mark.to != null && mark.to <= pos) nextMark();
-          if (mark) {
-            if (mark.from > pos) upto = Math.min(upto, mark.from);
-            else {
-              extraStyle += " " + mark.style;
-              if (mark.to != null) upto = Math.min(upto, mark.to);
-            }
-          }
-          for (;;) {
-            var end = pos + text.length;
-            var appliedStyle = style;
-            if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;
-            span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);
-            if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}
-            pos = end;
-            text = st[i++]; style = "cm-" + st[i++];
+            text = st[i++]; style = styleToClass(st[i++]);
           }
         }
-        if (sfrom != null && sto == null) span(" ", "CodeMirror-selected");
       }
-      if (includePre) html.push("
"); return html.join(""); + }, + cleanUp: function() { + this.parent = null; + if (this.marked) + for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this); } }; // Utility used by replace and split above @@ -2049,8 +2687,7 @@ var CodeMirror = (function() { if (state == 0) { if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]); if (end >= from) state = 1; - } - else if (state == 1) { + } else if (state == 1) { if (end > to) dest.push(part.slice(0, to - pos), source[i+1]); else dest.push(part, source[i+1]); } @@ -2058,36 +2695,229 @@ var CodeMirror = (function() { } } + // Data structure that holds the sequence of lines. + function LeafChunk(lines) { + this.lines = lines; + this.parent = null; + for (var i = 0, e = lines.length, height = 0; i < e; ++i) { + lines[i].parent = this; + height += lines[i].height; + } + this.height = height; + } + LeafChunk.prototype = { + chunkSize: function() { return this.lines.length; }, + remove: function(at, n, callbacks) { + for (var i = at, e = at + n; i < e; ++i) { + var line = this.lines[i]; + this.height -= line.height; + line.cleanUp(); + if (line.handlers) + for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]); + } + this.lines.splice(at, n); + }, + collapse: function(lines) { + lines.splice.apply(lines, [lines.length, 0].concat(this.lines)); + }, + insertHeight: function(at, lines, height) { + this.height += height; + this.lines = this.lines.slice(0, at).concat(lines).concat(this.lines.slice(at)); + for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this; + }, + iterN: function(at, n, op) { + for (var e = at + n; at < e; ++at) + if (op(this.lines[at])) return true; + } + }; + function BranchChunk(children) { + this.children = children; + var size = 0, height = 0; + for (var i = 0, e = children.length; i < e; ++i) { + var ch = children[i]; + size += ch.chunkSize(); height += ch.height; + ch.parent = this; + } + this.size = size; + this.height = height; + this.parent = null; + } + BranchChunk.prototype = { + chunkSize: function() { return this.size; }, + remove: function(at, n, callbacks) { + this.size -= n; + for (var i = 0; i < this.children.length; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var rm = Math.min(n, sz - at), oldHeight = child.height; + child.remove(at, rm, callbacks); + this.height -= oldHeight - child.height; + if (sz == rm) { this.children.splice(i--, 1); child.parent = null; } + if ((n -= rm) == 0) break; + at = 0; + } else at -= sz; + } + if (this.size - n < 25) { + var lines = []; + this.collapse(lines); + this.children = [new LeafChunk(lines)]; + this.children[0].parent = this; + } + }, + collapse: function(lines) { + for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines); + }, + insert: function(at, lines) { + var height = 0; + for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height; + this.insertHeight(at, lines, height); + }, + insertHeight: function(at, lines, height) { + this.size += lines.length; + this.height += height; + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at <= sz) { + child.insertHeight(at, lines, height); + if (child.lines && child.lines.length > 50) { + while (child.lines.length > 50) { + var spilled = child.lines.splice(child.lines.length - 25, 25); + var newleaf = new LeafChunk(spilled); + child.height -= newleaf.height; + this.children.splice(i + 1, 0, newleaf); + newleaf.parent = this; + } + this.maybeSpill(); + } + break; + } + at -= sz; + } + }, + maybeSpill: function() { + if (this.children.length <= 10) return; + var me = this; + do { + var spilled = me.children.splice(me.children.length - 5, 5); + var sibling = new BranchChunk(spilled); + if (!me.parent) { // Become the parent node + var copy = new BranchChunk(me.children); + copy.parent = me; + me.children = [copy, sibling]; + me = copy; + } else { + me.size -= sibling.size; + me.height -= sibling.height; + var myIndex = indexOf(me.parent.children, me); + me.parent.children.splice(myIndex + 1, 0, sibling); + } + sibling.parent = me.parent; + } while (me.children.length > 10); + me.parent.maybeSpill(); + }, + iter: function(from, to, op) { this.iterN(from, to - from, op); }, + iterN: function(at, n, op) { + for (var i = 0, e = this.children.length; i < e; ++i) { + var child = this.children[i], sz = child.chunkSize(); + if (at < sz) { + var used = Math.min(n, sz - at); + if (child.iterN(at, used, op)) return true; + if ((n -= used) == 0) break; + at = 0; + } else at -= sz; + } + } + }; + + function getLineAt(chunk, n) { + while (!chunk.lines) { + for (var i = 0;; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; break; } + n -= sz; + } + } + return chunk.lines[n]; + } + function lineNo(line) { + if (line.parent == null) return null; + var cur = line.parent, no = indexOf(cur.lines, line); + for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) { + for (var i = 0, e = chunk.children.length; ; ++i) { + if (chunk.children[i] == cur) break; + no += chunk.children[i].chunkSize(); + } + } + return no; + } + function lineAtHeight(chunk, h) { + var n = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], ch = child.height; + if (h < ch) { chunk = child; continue outer; } + h -= ch; + n += child.chunkSize(); + } + return n; + } while (!chunk.lines); + for (var i = 0, e = chunk.lines.length; i < e; ++i) { + var line = chunk.lines[i], lh = line.height; + if (h < lh) break; + h -= lh; + } + return n + i; + } + function heightAtLine(chunk, n) { + var h = 0; + outer: do { + for (var i = 0, e = chunk.children.length; i < e; ++i) { + var child = chunk.children[i], sz = child.chunkSize(); + if (n < sz) { chunk = child; continue outer; } + n -= sz; + h += child.height; + } + return h; + } while (!chunk.lines); + for (var i = 0; i < n; ++i) h += chunk.lines[i].height; + return h; + } + // The history object 'chunks' changes that are made close together // and at almost the same time into bigger undoable units. function History() { this.time = 0; this.done = []; this.undone = []; + this.compound = 0; + this.closed = false; } History.prototype = { addChange: function(start, added, old) { this.undone.length = 0; - var time = +new Date, last = this.done[this.done.length - 1]; - if (time - this.time > 400 || !last || - last.start > start + added || last.start + last.added < start - last.added + last.old.length) - this.done.push({start: start, added: added, old: old}); - else { - var oldoff = 0; - if (start < last.start) { - for (var i = last.start - start - 1; i >= 0; --i) - last.old.unshift(old[i]); - last.added += last.start - start; - last.start = start; - } - else if (last.start < start) { - oldoff = start - last.start; - added += oldoff; - } - for (var i = last.added - oldoff, e = old.length; i < e; ++i) - last.old.push(old[i]); - if (last.added < added) last.added = added; + var time = +new Date, cur = this.done[this.done.length - 1], last = cur && cur[cur.length - 1]; + var dtime = time - this.time; + + if (this.compound && cur && !this.closed) { + cur.push({start: start, added: added, old: old}); + } else if (dtime > 400 || !last || this.closed || + last.start > start + old.length || last.start + last.added < start) { + this.done.push([{start: start, added: added, old: old}]); + this.closed = false; + } else { + var startBefore = Math.max(0, last.start - start), + endAfter = Math.max(0, (start + old.length) - (last.start + last.added)); + for (var i = startBefore; i > 0; --i) last.old.unshift(old[i - 1]); + for (var i = endAfter; i > 0; --i) last.old.push(old[old.length - i]); + if (startBefore) last.start = start; + last.added += added - (old.length - startBefore - endAfter); } this.time = time; + }, + startCompound: function() { + if (!this.compound++) this.closed = true; + }, + endCompound: function() { + if (!--this.compound) this.closed = true; } }; @@ -2107,6 +2937,10 @@ var CodeMirror = (function() { else e.cancelBubble = true; } function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);} + CodeMirror.e_stop = e_stop; + CodeMirror.e_preventDefault = e_preventDefault; + CodeMirror.e_stopPropagation = e_stopPropagation; + function e_target(e) {return e.target || e.srcElement;} function e_button(e) { if (e.which) return e.which; @@ -2115,60 +2949,76 @@ var CodeMirror = (function() { else if (e.button & 4) return 2; } + // Allow 3rd-party code to override event properties by adding an override + // object to an event object. + function e_prop(e, prop) { + var overridden = e.override && e.override.hasOwnProperty(prop); + return overridden ? e.override[prop] : e[prop]; + } + // Event handler registration. If disconnect is true, it'll return a // function that unregisters the handler. function connect(node, type, handler, disconnect) { - function wrapHandler(event) {handler(event || window.event);} if (typeof node.addEventListener == "function") { - node.addEventListener(type, wrapHandler, false); - if (disconnect) return function() {node.removeEventListener(type, wrapHandler, false);}; - } - else { + node.addEventListener(type, handler, false); + if (disconnect) return function() {node.removeEventListener(type, handler, false);}; + } else { + var wrapHandler = function(event) {handler(event || window.event);}; node.attachEvent("on" + type, wrapHandler); if (disconnect) return function() {node.detachEvent("on" + type, wrapHandler);}; } } + CodeMirror.connect = connect; function Delayed() {this.id = null;} Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}}; - // Some IE versions don't preserve whitespace when setting the - // innerHTML of a PRE tag. - var badInnerHTML = (function() { - var pre = document.createElement("pre"); - pre.innerHTML = " "; return !pre.innerHTML; - })(); - - // Detect drag-and-drop - var dragAndDrop = (function() { - // IE8 has ondragstart and ondrop properties, but doesn't seem to - // actually support ondragstart the way it's supposed to work. - if (/MSIE [1-8]\b/.test(navigator.userAgent)) return false; - var div = document.createElement('div'); - return "ondragstart" in div && "ondrop" in div; - })(); + var Pass = CodeMirror.Pass = {toString: function(){return "CodeMirror.Pass";}}; var gecko = /gecko\/\d{7}/i.test(navigator.userAgent); var ie = /MSIE \d/.test(navigator.userAgent); + var ie_lt8 = /MSIE [1-7]\b/.test(navigator.userAgent); + var ie_lt9 = /MSIE [1-8]\b/.test(navigator.userAgent); + var quirksMode = ie && document.documentMode == 5; + var webkit = /WebKit\//.test(navigator.userAgent); + var chrome = /Chrome\//.test(navigator.userAgent); + var opera = /Opera\//.test(navigator.userAgent); var safari = /Apple Computer/.test(navigator.vendor); + var khtml = /KHTML\//.test(navigator.userAgent); + var mac_geLion = /Mac OS X 10\D([7-9]|\d\d)\D/.test(navigator.userAgent); - var lineSep = "\n"; + // Detect drag-and-drop + var dragAndDrop = function() { + // There is *some* kind of drag-and-drop support in IE6-8, but I + // couldn't get it to work yet. + if (ie_lt9) return false; + var div = document.createElement('div'); + return "draggable" in div || "dragDrop" in div; + }(); + // Feature-detect whether newlines in textareas are converted to \r\n - (function () { + var lineSep = function () { var te = document.createElement("textarea"); te.value = "foo\nbar"; - if (te.value.indexOf("\r") > -1) lineSep = "\r\n"; - }()); + if (te.value.indexOf("\r") > -1) return "\r\n"; + return "\n"; + }(); - var tabSize = 8; - var mac = /Mac/.test(navigator.platform); - var movementKeys = {}; - for (var i = 35; i <= 40; ++i) - movementKeys[i] = movementKeys["c" + i] = true; + // For a reason I have yet to figure out, some browsers disallow + // word wrapping between certain characters *only* if a new inline + // element is started between them. This makes it hard to reliably + // measure the position of things, since that requires inserting an + // extra span. This terribly fragile set of regexps matches the + // character combinations that suffer from this phenomenon on the + // various browsers. + var spanAffectsWrapping = /^$/; // Won't match any two-character string + if (gecko) spanAffectsWrapping = /$'/; + else if (safari) spanAffectsWrapping = /\-[^ \-?]|\?[^ !'\"\),.\-\/:;\?\]\}]/; + else if (chrome) spanAffectsWrapping = /\-[^ \-\.?]|\?[^ \-\.?\]\}:;!'\"\),\/]|[\.!\"#&%\)*+,:;=>\]|\}~][\(\{\[<]|\$'/; // Counts the column offset in a string, taking tabs into account. // Used mostly to find indentation. - function countColumn(string, end) { + function countColumn(string, end, tabSize) { if (end == null) { end = string.search(/[^\s\u00a0]/); if (end == -1) end = string.length; @@ -2184,25 +3034,54 @@ var CodeMirror = (function() { if (elt.currentStyle) return elt.currentStyle; return window.getComputedStyle(elt, null); } + // Find the position of an element by following the offsetParent chain. // If screen==true, it returns screen (rather than page) coordinates. function eltOffset(node, screen) { - var doc = node.ownerDocument.body; - var x = 0, y = 0, skipDoc = false; + var bod = node.ownerDocument.body; + var x = 0, y = 0, skipBody = false; for (var n = node; n; n = n.offsetParent) { - x += n.offsetLeft; y += n.offsetTop; + var ol = n.offsetLeft, ot = n.offsetTop; + // Firefox reports weird inverted offsets when the body has a border. + if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); } + else { x += ol, y += ot; } if (screen && computedStyle(n).position == "fixed") - skipDoc = true; + skipBody = true; } - var e = screen && !skipDoc ? null : doc; + var e = screen && !skipBody ? null : bod; for (var n = node.parentNode; n != e; n = n.parentNode) if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;} return {left: x, top: y}; } + // Use the faster and saner getBoundingClientRect method when possible. + if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) { + // Take the parts of bounding client rect that we are interested in so we are able to edit if need be, + // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page) + try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; } + catch(e) { box = {top: 0, left: 0}; } + if (!screen) { + // Get the toplevel scroll, working around browser differences. + if (window.pageYOffset == null) { + var t = document.documentElement || document.body.parentNode; + if (t.scrollTop == null) t = document.body; + box.top += t.scrollTop; box.left += t.scrollLeft; + } else { + box.top += window.pageYOffset; box.left += window.pageXOffset; + } + } + return box; + }; + // Get a node's text content. function eltText(node) { return node.textContent || node.innerText || node.nodeValue || ""; } + function selectInput(node) { + if (ios) { // Mobile Safari apparently has a bug where select() is broken. + node.selectionStart = 0; + node.selectionEnd = node.value.length; + } else node.select(); + } // Operations on {line, ch} objects. function posEq(a, b) {return a.line == b.line && a.ch == b.ch;} @@ -2211,21 +3090,30 @@ var CodeMirror = (function() { var escapeElement = document.createElement("pre"); function htmlEscape(str) { - if (badTextContent) { + escapeElement.textContent = str; + return escapeElement.innerHTML; + } + // Recent (late 2011) Opera betas insert bogus newlines at the start + // of the textContent, so we strip those. + if (htmlEscape("a") == "\na") { + htmlEscape = function(str) { + escapeElement.textContent = str; + return escapeElement.innerHTML.slice(1); + }; + // Some IEs don't preserve tabs through innerHTML + } else if (htmlEscape("\t") != "\t") { + htmlEscape = function(str) { escapeElement.innerHTML = ""; escapeElement.appendChild(document.createTextNode(str)); - } else { - escapeElement.textContent = str; - } - return escapeElement.innerHTML; + return escapeElement.innerHTML; + }; } - var badTextContent = htmlEscape("\t") != "\t"; CodeMirror.htmlEscape = htmlEscape; // Used to position the cursor after an undo/redo by finding the // last edited character. function editEnd(from, to) { - if (!to) return from ? from.length : 0; + if (!to) return 0; if (!from) return to.length; for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j) if (from.charAt(i) != to.charAt(j)) break; @@ -2238,95 +3126,54 @@ var CodeMirror = (function() { if (collection[i] == elt) return i; return -1; } + function isWordChar(ch) { + return /\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase(); + } // See if "".split is the broken IE version, if so, provide an // alternative way to split lines. - var splitLines, selRange, setSelRange; - if ("\n\nb".split(/\n/).length != 3) - splitLines = function(string) { - var pos = 0, nl, result = []; - while ((nl = string.indexOf("\n", pos)) > -1) { - result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); - pos = nl + 1; - } - result.push(string.slice(pos)); - return result; - }; - else - splitLines = function(string){return string.split(/\r?\n/);}; + var splitLines = "\n\nb".split(/\n/).length != 3 ? function(string) { + var pos = 0, nl, result = []; + while ((nl = string.indexOf("\n", pos)) > -1) { + result.push(string.slice(pos, string.charAt(nl-1) == "\r" ? nl - 1 : nl)); + pos = nl + 1; + } + result.push(string.slice(pos)); + return result; + } : function(string){return string.split(/\r?\n/);}; CodeMirror.splitLines = splitLines; - // Sane model of finding and setting the selection in a textarea - if (window.getSelection) { - selRange = function(te) { - try {return {start: te.selectionStart, end: te.selectionEnd};} - catch(e) {return null;} - }; - if (safari) - // On Safari, selection set with setSelectionRange are in a sort - // of limbo wrt their anchor. If you press shift-left in them, - // the anchor is put at the end, and the selection expanded to - // the left. If you press shift-right, the anchor ends up at the - // front. This is not what CodeMirror wants, so it does a - // spurious modify() call to get out of limbo. - setSelRange = function(te, start, end) { - if (start == end) - te.setSelectionRange(start, end); - else { - te.setSelectionRange(start, end - 1); - window.getSelection().modify("extend", "forward", "character"); - } - }; - else - setSelRange = function(te, start, end) { - try {te.setSelectionRange(start, end);} - catch(e) {} // Fails on Firefox when textarea isn't part of the document - }; - } - // IE model. Don't ask. - else { - selRange = function(te) { - try {var range = te.ownerDocument.selection.createRange();} - catch(e) {return null;} - if (!range || range.parentElement() != te) return null; - var val = te.value, len = val.length, localRange = te.createTextRange(); - localRange.moveToBookmark(range.getBookmark()); - var endRange = te.createTextRange(); - endRange.collapse(false); - - if (localRange.compareEndPoints("StartToEnd", endRange) > -1) - return {start: len, end: len}; - - var start = -localRange.moveStart("character", -len); - for (var i = val.indexOf("\r"); i > -1 && i < start; i = val.indexOf("\r", i+1), start++) {} - - if (localRange.compareEndPoints("EndToEnd", endRange) > -1) - return {start: start, end: len}; - - var end = -localRange.moveEnd("character", -len); - for (var i = val.indexOf("\r"); i > -1 && i < end; i = val.indexOf("\r", i+1), end++) {} - return {start: start, end: end}; - }; - setSelRange = function(te, start, end) { - var range = te.createTextRange(); - range.collapse(true); - var endrange = range.duplicate(); - var newlines = 0, txt = te.value; - for (var pos = txt.indexOf("\n"); pos > -1 && pos < start; pos = txt.indexOf("\n", pos + 1)) - ++newlines; - range.move("character", start - newlines); - for (; pos > -1 && pos < end; pos = txt.indexOf("\n", pos + 1)) - ++newlines; - endrange.move("character", end - newlines); - range.setEndPoint("EndToEnd", endrange); - range.select(); - }; - } + var hasSelection = window.getSelection ? function(te) { + try { return te.selectionStart != te.selectionEnd; } + catch(e) { return false; } + } : function(te) { + try {var range = te.ownerDocument.selection.createRange();} + catch(e) {} + if (!range || range.parentElement() != te) return false; + return range.compareEndPoints("StartToEnd", range) != 0; + }; CodeMirror.defineMode("null", function() { return {token: function(stream) {stream.skipToEnd();}}; }); CodeMirror.defineMIME("text/plain", "null"); + var keyNames = {3: "Enter", 8: "Backspace", 9: "Tab", 13: "Enter", 16: "Shift", 17: "Ctrl", 18: "Alt", + 19: "Pause", 20: "CapsLock", 27: "Esc", 32: "Space", 33: "PageUp", 34: "PageDown", 35: "End", + 36: "Home", 37: "Left", 38: "Up", 39: "Right", 40: "Down", 44: "PrintScrn", 45: "Insert", + 46: "Delete", 59: ";", 91: "Mod", 92: "Mod", 93: "Mod", 109: "-", 107: "=", 127: "Delete", + 186: ";", 187: "=", 188: ",", 189: "-", 190: ".", 191: "/", 192: "`", 219: "[", 220: "\\", + 221: "]", 222: "'", 63276: "PageUp", 63277: "PageDown", 63275: "End", 63273: "Home", + 63234: "Left", 63232: "Up", 63235: "Right", 63233: "Down", 63302: "Insert", 63272: "Delete"}; + CodeMirror.keyNames = keyNames; + (function() { + // Number keys + for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i); + // Alphabetic keys + for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i); + // Function keys + for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = "F" + i; + })(); + return CodeMirror; })(); diff --git a/rhodecode/public/js/native.history.js b/rhodecode/public/js/native.history.js new file mode 100644 --- /dev/null +++ b/rhodecode/public/js/native.history.js @@ -0,0 +1,1 @@ +(function(a,b){"use strict";var c=a.History=a.History||{};if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={handlers:{},_uid:1,uid:function(a){return a._uid||(a._uid=c.Adapter._uid++)},bind:function(a,b,d){var e=c.Adapter.uid(a);c.Adapter.handlers[e]=c.Adapter.handlers[e]||{},c.Adapter.handlers[e][b]=c.Adapter.handlers[e][b]||[],c.Adapter.handlers[e][b].push(d),a["on"+b]=function(a,b){return function(d){c.Adapter.trigger(a,b,d)}}(a,b)},trigger:function(a,b,d){d=d||{};var e=c.Adapter.uid(a),f,g;c.Adapter.handlers[e]=c.Adapter.handlers[e]||{},c.Adapter.handlers[e][b]=c.Adapter.handlers[e][b]||[];for(f=0,g=c.Adapter.handlers[e][b].length;f")&&c[0]);return a>4?a:!1}();return a},m.isInternetExplorer=function(){var a=m.isInternetExplorer.cached=typeof m.isInternetExplorer.cached!="undefined"?m.isInternetExplorer.cached:Boolean(m.getInternetExplorerMajorVersion());return a},m.emulated={pushState:!Boolean(a.history&&a.history.pushState&&a.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(e.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(e.userAgent)),hashChange:Boolean(!("onhashchange"in a||"onhashchange"in d)||m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8)},m.enabled=!m.emulated.pushState,m.bugs={setHash:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),safariPoll:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),ieDoubleCheck:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<7)},m.isEmptyObject=function(a){for(var b in a)return!1;return!0},m.cloneObject=function(a){var b,c;return a?(b=k.stringify(a),c=k.parse(b)):c={},c},m.getRootUrl=function(){var a=d.location.protocol+"//"+(d.location.hostname||d.location.host);if(d.location.port||!1)a+=":"+d.location.port;return a+="/",a},m.getBaseHref=function(){var a=d.getElementsByTagName("base"),b=null,c="";return a.length===1&&(b=a[0],c=b.href.replace(/[^\/]+$/,"")),c=c.replace(/\/+$/,""),c&&(c+="/"),c},m.getBaseUrl=function(){var a=m.getBaseHref()||m.getBasePageUrl()||m.getRootUrl();return a},m.getPageUrl=function(){var a=m.getState(!1,!1),b=(a||{}).url||d.location.href,c;return c=b.replace(/\/+$/,"").replace(/[^\/]+$/,function(a,b,c){return/\./.test(a)?a:a+"/"}),c},m.getBasePageUrl=function(){var a=d.location.href.replace(/[#\?].*/,"").replace(/[^\/]+$/,function(a,b,c){return/[^\/]$/.test(a)?"":a}).replace(/\/+$/,"")+"/";return a},m.getFullUrl=function(a,b){var c=a,d=a.substring(0,1);return b=typeof b=="undefined"?!0:b,/[a-z]+\:\/\//.test(a)||(d==="/"?c=m.getRootUrl()+a.replace(/^\/+/,""):d==="#"?c=m.getPageUrl().replace(/#.*/,"")+a:d==="?"?c=m.getPageUrl().replace(/[\?#].*/,"")+a:b?c=m.getBaseUrl()+a.replace(/^(\.\/)+/,""):c=m.getBasePageUrl()+a.replace(/^(\.\/)+/,"")),c.replace(/\#$/,"")},m.getShortUrl=function(a){var b=a,c=m.getBaseUrl(),d=m.getRootUrl();return m.emulated.pushState&&(b=b.replace(c,"")),b=b.replace(d,"/"),m.isTraditionalAnchor(b)&&(b="./"+b),b=b.replace(/^(\.\/)+/g,"./").replace(/\#$/,""),b},m.store={},m.idToState=m.idToState||{},m.stateToId=m.stateToId||{},m.urlToId=m.urlToId||{},m.storedStates=m.storedStates||[],m.savedStates=m.savedStates||[],m.normalizeStore=function(){m.store.idToState=m.store.idToState||{},m.store.urlToId=m.store.urlToId||{},m.store.stateToId=m.store.stateToId||{}},m.getState=function(a,b){typeof a=="undefined"&&(a=!0),typeof b=="undefined"&&(b=!0);var c=m.getLastSavedState();return!c&&b&&(c=m.createStateObject()),a&&(c=m.cloneObject(c),c.url=c.cleanUrl||c.url),c},m.getIdByState=function(a){var b=m.extractId(a.url),c;if(!b){c=m.getStateString(a);if(typeof m.stateToId[c]!="undefined")b=m.stateToId[c];else if(typeof m.store.stateToId[c]!="undefined")b=m.store.stateToId[c];else{for(;;){b=(new Date).getTime()+String(Math.random()).replace(/\D/g,"");if(typeof m.idToState[b]=="undefined"&&typeof m.store.idToState[b]=="undefined")break}m.stateToId[c]=b,m.idToState[b]=a}}return b},m.normalizeState=function(a){var b,c;if(!a||typeof a!="object")a={};if(typeof a.normalized!="undefined")return a;if(!a.data||typeof a.data!="object")a.data={};b={},b.normalized=!0,b.title=a.title||"",b.url=m.getFullUrl(m.unescapeString(a.url||d.location.href)),b.hash=m.getShortUrl(b.url),b.data=m.cloneObject(a.data),b.id=m.getIdByState(b),b.cleanUrl=b.url.replace(/\??\&_suid.*/,""),b.url=b.cleanUrl,c=!m.isEmptyObject(b.data);if(b.title||c)b.hash=m.getShortUrl(b.url).replace(/\??\&_suid.*/,""),/\?/.test(b.hash)||(b.hash+="?"),b.hash+="&_suid="+b.id;return b.hashedUrl=m.getFullUrl(b.hash),(m.emulated.pushState||m.bugs.safariPoll)&&m.hasUrlDuplicate(b)&&(b.url=b.hashedUrl),b},m.createStateObject=function(a,b,c){var d={data:a,title:b,url:c};return d=m.normalizeState(d),d},m.getStateById=function(a){a=String(a);var c=m.idToState[a]||m.store.idToState[a]||b;return c},m.getStateString=function(a){var b,c,d;return b=m.normalizeState(a),c={data:b.data,title:a.title,url:a.url},d=k.stringify(c),d},m.getStateId=function(a){var b,c;return b=m.normalizeState(a),c=b.id,c},m.getHashByState=function(a){var b,c;return b=m.normalizeState(a),c=b.hash,c},m.extractId=function(a){var b,c,d;return c=/(.*)\&_suid=([0-9]+)$/.exec(a),d=c?c[1]||a:a,b=c?String(c[2]||""):"",b||!1},m.isTraditionalAnchor=function(a){var b=!/[\/\?\.]/.test(a);return b},m.extractState=function(a,b){var c=null,d,e;return b=b||!1,d=m.extractId(a),d&&(c=m.getStateById(d)),c||(e=m.getFullUrl(a),d=m.getIdByUrl(e)||!1,d&&(c=m.getStateById(d)),!c&&b&&!m.isTraditionalAnchor(a)&&(c=m.createStateObject(null,null,e))),c},m.getIdByUrl=function(a){var c=m.urlToId[a]||m.store.urlToId[a]||b;return c},m.getLastSavedState=function(){return m.savedStates[m.savedStates.length-1]||b},m.getLastStoredState=function(){return m.storedStates[m.storedStates.length-1]||b},m.hasUrlDuplicate=function(a){var b=!1,c;return c=m.extractState(a.url),b=c&&c.id!==a.id,b},m.storeState=function(a){return m.urlToId[a.url]=a.id,m.storedStates.push(m.cloneObject(a)),a},m.isLastSavedState=function(a){var b=!1,c,d,e;return m.savedStates.length&&(c=a.id,d=m.getLastSavedState(),e=d.id,b=c===e),b},m.saveState=function(a){return m.isLastSavedState(a)?!1:(m.savedStates.push(m.cloneObject(a)),!0)},m.getStateByIndex=function(a){var b=null;return typeof a=="undefined"?b=m.savedStates[m.savedStates.length-1]:a<0?b=m.savedStates[m.savedStates.length+a]:b=m.savedStates[a],b},m.getHash=function(){var a=m.unescapeHash(d.location.hash);return a},m.unescapeString=function(b){var c=b,d;for(;;){d=a.decodeURI(c);if(d===c)break;c=d}return c},m.unescapeHash=function(a){var b=m.normalizeHash(a);return b=m.unescapeString(b),b},m.normalizeHash=function(a){var b=a.replace(/[^#]*#/,"").replace(/#.*/,"");return b},m.setHash=function(a,b){var c,e,f;return b!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.setHash,args:arguments,queue:b}),!1):(c=m.escapeHash(a),m.busy(!0),e=m.extractState(a,!0),e&&!m.emulated.pushState?m.pushState(e.data,e.title,e.url,!1):d.location.hash!==c&&(m.bugs.setHash?(f=m.getPageUrl(),m.pushState(null,null,f+"#"+c,!1)):d.location.hash=c),m)},m.escapeHash=function(b){var c=m.normalizeHash(b);return c=a.escape(c),m.bugs.hashEscape||(c=c.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?")),c},m.getHashByUrl=function(a){var b=String(a).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");return b=m.unescapeHash(b),b},m.setTitle=function(a){var b=a.title,c;b||(c=m.getStateByIndex(0),c&&c.url===a.url&&(b=c.title||m.options.initialTitle));try{d.getElementsByTagName("title")[0].innerHTML=b.replace("<","<").replace(">",">").replace(" & "," & ")}catch(e){}return d.title=b,m},m.queues=[],m.busy=function(a){typeof a!="undefined"?m.busy.flag=a:typeof m.busy.flag=="undefined"&&(m.busy.flag=!1);if(!m.busy.flag){h(m.busy.timeout);var b=function(){var a,c,d;if(m.busy.flag)return;for(a=m.queues.length-1;a>=0;--a){c=m.queues[a];if(c.length===0)continue;d=c.shift(),m.fireQueueItem(d),m.busy.timeout=g(b,m.options.busyDelay)}};m.busy.timeout=g(b,m.options.busyDelay)}return m.busy.flag},m.busy.flag=!1,m.fireQueueItem=function(a){return a.callback.apply(a.scope||m,a.args||[])},m.pushQueue=function(a){return m.queues[a.queue||0]=m.queues[a.queue||0]||[],m.queues[a.queue||0].push(a),m},m.queue=function(a,b){return typeof a=="function"&&(a={callback:a}),typeof b!="undefined"&&(a.queue=b),m.busy()?m.pushQueue(a):m.fireQueueItem(a),m},m.clearQueue=function(){return m.busy.flag=!1,m.queues=[],m},m.stateChanged=!1,m.doubleChecker=!1,m.doubleCheckComplete=function(){return m.stateChanged=!0,m.doubleCheckClear(),m},m.doubleCheckClear=function(){return m.doubleChecker&&(h(m.doubleChecker),m.doubleChecker=!1),m},m.doubleCheck=function(a){return m.stateChanged=!1,m.doubleCheckClear(),m.bugs.ieDoubleCheck&&(m.doubleChecker=g(function(){return m.doubleCheckClear(),m.stateChanged||a(),!0},m.options.doubleCheckInterval)),m},m.safariStatePoll=function(){var b=m.extractState(d.location.href),c;if(!m.isLastSavedState(b))c=b;else return;return c||(c=m.createStateObject()),m.Adapter.trigger(a,"popstate"),m},m.back=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.back,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.back(!1)}),n.go(-1),!0)},m.forward=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.forward,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.forward(!1)}),n.go(1),!0)},m.go=function(a,b){var c;if(a>0)for(c=1;c<=a;++c)m.forward(b);else{if(!(a<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(c=-1;c>=a;--c)m.back(b)}return m};if(m.emulated.pushState){var o=function(){};m.pushState=m.pushState||o,m.replaceState=m.replaceState||o}else m.onPopState=function(b,c){var e=!1,f=!1,g,h;return m.doubleCheckComplete(),g=m.getHash(),g?(h=m.extractState(g||d.location.href,!0),h?m.replaceState(h.data,h.title,h.url,!1):(m.Adapter.trigger(a,"anchorchange"),m.busy(!1)),m.expectedStateId=!1,!1):(e=m.Adapter.extractEventData("state",b,c)||!1,e?f=m.getStateById(e):m.expectedStateId?f=m.getStateById(m.expectedStateId):f=m.extractState(d.location.href),f||(f=m.createStateObject(null,null,d.location.href)),m.expectedStateId=!1,m.isLastSavedState(f)?(m.busy(!1),!1):(m.storeState(f),m.saveState(f),m.setTitle(f),m.Adapter.trigger(a,"statechange"),m.busy(!1),!0))},m.Adapter.bind(a,"popstate",m.onPopState),m.pushState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.pushState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.pushState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0},m.replaceState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.replaceState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.replaceState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0};if(f){try{m.store=k.parse(f.getItem("History.store"))||{}}catch(p){m.store={}}m.normalizeStore()}else m.store={},m.normalizeStore();m.Adapter.bind(a,"beforeunload",m.clearAllIntervals),m.Adapter.bind(a,"unload",m.clearAllIntervals),m.saveState(m.storeState(m.extractState(d.location.href,!0))),f&&(m.onUnload=function(){var a,b;try{a=k.parse(f.getItem("History.store"))||{}}catch(c){a={}}a.idToState=a.idToState||{},a.urlToId=a.urlToId||{},a.stateToId=a.stateToId||{};for(b in m.idToState){if(!m.idToState.hasOwnProperty(b))continue;a.idToState[b]=m.idToState[b]}for(b in m.urlToId){if(!m.urlToId.hasOwnProperty(b))continue;a.urlToId[b]=m.urlToId[b]}for(b in m.stateToId){if(!m.stateToId.hasOwnProperty(b))continue;a.stateToId[b]=m.stateToId[b]}m.store=a,m.normalizeStore(),f.setItem("History.store",k.stringify(a))},m.intervalList.push(i(m.onUnload,m.options.storeInterval)),m.Adapter.bind(a,"beforeunload",m.onUnload),m.Adapter.bind(a,"unload",m.onUnload));if(!m.emulated.pushState){m.bugs.safariPoll&&m.intervalList.push(i(m.safariStatePoll,m.options.safariPollInterval));if(e.vendor==="Apple Computer, Inc."||(e.appCodeName||"")==="Mozilla")m.Adapter.bind(a,"hashchange",function(){m.Adapter.trigger(a,"popstate")}),m.getHash()&&m.Adapter.onDomLoad(function(){m.Adapter.trigger(a,"hashchange")})}},m.init()}(window) \ No newline at end of file diff --git a/rhodecode/public/js/rhodecode.js b/rhodecode/public/js/rhodecode.js --- a/rhodecode/public/js/rhodecode.js +++ b/rhodecode/public/js/rhodecode.js @@ -44,6 +44,50 @@ String.prototype.format = function() { }(); +String.prototype.strip = function(char) { + if(char === undefined){ + char = '\\s'; + } + return this.replace(new RegExp('^'+char+'+|'+char+'+$','g'), ''); +} +String.prototype.lstrip = function(char) { + if(char === undefined){ + char = '\\s'; + } + return this.replace(new RegExp('^'+char+'+'),''); +} +String.prototype.rstrip = function(char) { + if(char === undefined){ + char = '\\s'; + } + return this.replace(new RegExp(''+char+'+$'),''); +} + + +if(!Array.prototype.indexOf) { + Array.prototype.indexOf = function(needle) { + for(var i = 0; i < this.length; i++) { + if(this[i] === needle) { + return i; + } + } + return -1; + }; +} + +// IE(CRAP) doesn't support previousElementSibling +var prevElementSibling = function( el ) { + if( el.previousElementSibling ) { + return el.previousElementSibling; + } else { + while( el = el.previousSibling ) { + if( el.nodeType === 1 ) return el; + } + } +} + + + /** * SmartColorGenerator @@ -187,10 +231,10 @@ function ypjax(url,container,s_call,f_ca success:s_wrapper, failure:function(o){ console.log(o); - YUD.get(container).innerHTML='ERROR'; + YUD.get(container).innerHTML='ERROR: {0}'.format(o.status); YUD.setStyle(container,'opacity','1.0'); - YUD.setStyle(container,'color','red'); - } + }, + cache:false },args); }; @@ -354,7 +398,7 @@ var createInlineForm = function(parent_t // create event for hide button form = new YAHOO.util.Element(form); - var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]); + var form_hide_button = new YAHOO.util.Element(YUD.getElementsByClassName('hide-inline-form',null,form)[0]); form_hide_button.on('click', function(e) { var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode; if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){ @@ -378,11 +422,12 @@ var injectInlineForm = function(tr){ return } var submit_url = AJAX_COMMENT_URL; - if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){ + var _td = YUD.getElementsByClassName('code',null,tr)[0]; + if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(_td,'no-comment')){ return } YUD.addClass(tr,'form-open'); - var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0]; + var node = YUD.getElementsByClassName('full_f_path',null,tr.parentNode.parentNode.parentNode)[0]; var f_path = YUD.getAttribute(node,'path'); var lineno = getLineNo(tr); var form = createInlineForm(tr, f_path, lineno, submit_url); @@ -400,11 +445,10 @@ var injectInlineForm = function(tr){ } YUD.insertAfter(form,parent); - YUD.get('text_'+lineno).focus(); var f = YUD.get(form); - var overlay = f.getElementsByClassName('overlay')[0]; - var _form = f.getElementsByClassName('inline-form')[0]; + var overlay = YUD.getElementsByClassName('overlay',null,f)[0]; + var _form = YUD.getElementsByClassName('inline-form',null,f)[0]; form.on('submit',function(e){ YUE.preventDefault(e); @@ -448,7 +492,16 @@ var injectInlineForm = function(tr){ ajaxPOST(submit_url, postData, success); }); - tooltip_activate(); + setTimeout(function(){ + // callbacks + tooltip_activate(); + MentionsAutoComplete('text_'+lineno, 'mentions_container_'+lineno, + _USERS_AC_DATA, _GROUPS_AC_DATA); + var _e = YUD.get('text_'+lineno); + if(_e){ + _e.focus(); + } + },10) }; var deleteComment = function(comment_id){ @@ -456,7 +509,7 @@ var deleteComment = function(comment_id) var postData = {'_method':'delete'}; var success = function(o){ var n = YUD.get('comment-tr-'+comment_id); - var root = n.previousElementSibling.previousElementSibling; + var root = prevElementSibling(prevElementSibling(n)); n.parentNode.removeChild(n); // scann nodes, and attach add button to last one @@ -465,6 +518,15 @@ var deleteComment = function(comment_id) ajaxPOST(url,postData,success); } +var updateReviewers = function(reviewers_ids){ + var url = AJAX_UPDATE_PULLREQUEST; + var postData = {'_method':'put', + 'reviewers_ids': reviewers_ids}; + var success = function(o){ + window.location.reload(); + } + ajaxPOST(url,postData,success); +} var createInlineAddButton = function(tr){ @@ -506,8 +568,8 @@ var placeAddButton = function(target_tr) // next element are comments ! if(YUD.hasClass(n,'inline-comments')){ last_node = n; - //also remove the comment button from previos - var comment_add_buttons = last_node.getElementsByClassName('add-comment'); + //also remove the comment button from previous + var comment_add_buttons = YUD.getElementsByClassName('add-comment',null,last_node); for(var i=0;i{0}".format(n.substring(pos,pos+query.length)) - +n.substring(pos+query.length) - match.push('
'.format(t,node_url.replace('__FPATH__',n),n_hl)); + +n.substring(pos+query.length) + node_url = node_url.replace('__FPATH__',n); + match.push(''.format(t,node_url,n_hl)); } if(match.length >= matches_max){ - match.push(''.format(truncated_lbl)); + match.push(''.format(_TM['search truncated'])); } - } } if(query != ""){ @@ -677,7 +745,7 @@ var fileBrowserListeners = function(curr YUD.setStyle('tbody_filtered','display',''); if (match.length==0){ - match.push(''.format(nomatch_lbl)); + match.push(''.format(_TM['no matching files'])); } YUD.get('tbody_filtered').innerHTML = match.join(""); @@ -816,10 +884,31 @@ var deleteNotification = function(url, n callback, postData); }; +var readNotification = function(url, notification_id,callbacks){ + var callback = { + success:function(o){ + var obj = YUD.get(String("notification_"+notification_id)); + YUD.removeClass(obj, 'unread'); + var r_button = YUD.getElementsByClassName('read-notification',null,obj.children[0])[0]; + + if(r_button.parentNode !== undefined){ + r_button.parentNode.removeChild(r_button); + } + _run_callbacks(callbacks); + }, + failure:function(o){ + alert("error"); + }, + }; + var postData = '_method=put'; + var sUrl = url.replace('__NOTIFICATION_ID__',notification_id); + var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, + callback, postData); +}; /** MEMBERS AUTOCOMPLETE WIDGET **/ -var MembersAutoComplete = function (users_list, groups_list, group_lbl, members_lbl) { +var MembersAutoComplete = function (divid, cont, users_list, groups_list) { var myUsers = users_list; var myGroups = groups_list; @@ -834,9 +923,11 @@ var MembersAutoComplete = function (user // Match against each name of each contact for (; i < l; i++) { contact = myUsers[i]; - if ((contact.fname.toLowerCase().indexOf(query) > -1) || (contact.lname.toLowerCase().indexOf(query) > -1) || (contact.nname && (contact.nname.toLowerCase().indexOf(query) > -1))) { - matches[matches.length] = contact; - } + if (((contact.fname+"").toLowerCase().indexOf(query) > -1) || + ((contact.lname+"").toLowerCase().indexOf(query) > -1) || + ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) { + matches[matches.length] = contact; + } } return matches; }; @@ -879,15 +970,20 @@ var MembersAutoComplete = function (user }; // Instantiate AutoComplete for perms - var membersAC = new YAHOO.widget.AutoComplete("perm_new_member_name", "perm_container", memberDS); + var membersAC = new YAHOO.widget.AutoComplete(divid, cont, memberDS); membersAC.useShadow = false; membersAC.resultTypeList = false; + membersAC.animVert = false; + membersAC.animHoriz = false; + membersAC.animSpeed = 0.1; // Instantiate AutoComplete for owner var ownerAC = new YAHOO.widget.AutoComplete("user", "owner_container", ownerDS); ownerAC.useShadow = false; ownerAC.resultTypeList = false; - + ownerAC.animVert = false; + ownerAC.animHoriz = false; + ownerAC.animSpeed = 0.1; // Helper highlight function for the formatter var highlightMatch = function (full, snippet, matchindex) { @@ -912,21 +1008,22 @@ var MembersAutoComplete = function (user var grname = oResultData.grname; var grmembers = oResultData.grmembers; var grnameMatchIndex = grname.toLowerCase().indexOf(query); - var grprefix = "{0}: ".format(group_lbl); + var grprefix = "{0}: ".format(_TM['Group']); var grsuffix = " (" + grmembers + " )"; - var grsuffix = " ({0} {1})".format(grmembers, members_lbl); + var grsuffix = " ({0} {1})".format(grmembers, _TM['members']); if (grnameMatchIndex > -1) { return _gravatar(grprefix + highlightMatch(grname, query, grnameMatchIndex) + grsuffix,null,true); } return _gravatar(grprefix + oResultData.grname + grsuffix, null,true); // Users - } else if (oResultData.fname != undefined) { - var fname = oResultData.fname, - lname = oResultData.lname, - nname = oResultData.nname || "", - // Guard against null value - fnameMatchIndex = fname.toLowerCase().indexOf(query), + } else if (oResultData.nname != undefined) { + var fname = oResultData.fname || ""; + var lname = oResultData.lname || ""; + var nname = oResultData.nname; + + // Guard against null value + var fnameMatchIndex = fname.toLowerCase().indexOf(query), lnameMatchIndex = lname.toLowerCase().indexOf(query), nnameMatchIndex = nname.toLowerCase().indexOf(query), displayfname, displaylname, displaynname; @@ -958,7 +1055,7 @@ var MembersAutoComplete = function (user ownerAC.formatResult = custom_formatter; var myHandler = function (sType, aArgs) { - + var nextId = divid.split('perm_new_member_name_')[1]; var myAC = aArgs[0]; // reference back to the AC instance var elLI = aArgs[1]; // reference to the selected LI element var oData = aArgs[2]; // object literal of selected item's result data @@ -966,11 +1063,11 @@ var MembersAutoComplete = function (user if (oData.nname != undefined) { //users myAC.getInputEl().value = oData.nname; - YUD.get('perm_new_member_type').value = 'user'; + YUD.get('perm_new_member_type_'+nextId).value = 'user'; } else { //groups myAC.getInputEl().value = oData.grname; - YUD.get('perm_new_member_type').value = 'users_group'; + YUD.get('perm_new_member_type_'+nextId).value = 'users_group'; } }; @@ -988,6 +1085,359 @@ var MembersAutoComplete = function (user } +var MentionsAutoComplete = function (divid, cont, users_list, groups_list) { + var myUsers = users_list; + var myGroups = groups_list; + + // Define a custom search function for the DataSource of users + var matchUsers = function (sQuery) { + var org_sQuery = sQuery; + if(this.mentionQuery == null){ + return [] + } + sQuery = this.mentionQuery; + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myUsers.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + contact = myUsers[i]; + if (((contact.fname+"").toLowerCase().indexOf(query) > -1) || + ((contact.lname+"").toLowerCase().indexOf(query) > -1) || + ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) { + matches[matches.length] = contact; + } + } + return matches + }; + + //match all + var matchAll = function (sQuery) { + u = matchUsers(sQuery); + return u + }; + + // DataScheme for owner + var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers); + + ownerDS.responseSchema = { + fields: ["id", "fname", "lname", "nname", "gravatar_lnk"] + }; + + // Instantiate AutoComplete for mentions + var ownerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS); + ownerAC.useShadow = false; + ownerAC.resultTypeList = false; + ownerAC.suppressInputUpdate = true; + ownerAC.animVert = false; + ownerAC.animHoriz = false; + ownerAC.animSpeed = 0.1; + + // Helper highlight function for the formatter + var highlightMatch = function (full, snippet, matchindex) { + return full.substring(0, matchindex) + + "" + + full.substr(matchindex, snippet.length) + + "" + full.substring(matchindex + snippet.length); + }; + + // Custom formatter to highlight the matching letters + ownerAC.formatResult = function (oResultData, sQuery, sResultMatch) { + var org_sQuery = sQuery; + if(this.dataSource.mentionQuery != null){ + sQuery = this.dataSource.mentionQuery; + } + + var query = sQuery.toLowerCase(); + var _gravatar = function(res, em, group){ + if (group !== undefined){ + em = '/images/icons/group.png' + } + tmpl = '
{1}
' + return tmpl.format(em,res) + } + if (oResultData.nname != undefined) { + var fname = oResultData.fname || ""; + var lname = oResultData.lname || ""; + var nname = oResultData.nname; + + // Guard against null value + var fnameMatchIndex = fname.toLowerCase().indexOf(query), + lnameMatchIndex = lname.toLowerCase().indexOf(query), + nnameMatchIndex = nname.toLowerCase().indexOf(query), + displayfname, displaylname, displaynname; + + if (fnameMatchIndex > -1) { + displayfname = highlightMatch(fname, query, fnameMatchIndex); + } else { + displayfname = fname; + } + + if (lnameMatchIndex > -1) { + displaylname = highlightMatch(lname, query, lnameMatchIndex); + } else { + displaylname = lname; + } + + if (nnameMatchIndex > -1) { + displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")"; + } else { + displaynname = nname ? "(" + nname + ")" : ""; + } + + return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk); + } else { + return ''; + } + }; + + if(ownerAC.itemSelectEvent){ + ownerAC.itemSelectEvent.subscribe(function (sType, aArgs) { + + var myAC = aArgs[0]; // reference back to the AC instance + var elLI = aArgs[1]; // reference to the selected LI element + var oData = aArgs[2]; // object literal of selected item's result data + //fill the autocomplete with value + if (oData.nname != undefined) { + //users + //Replace the mention name with replaced + var re = new RegExp(); + var org = myAC.getInputEl().value; + var chunks = myAC.dataSource.chunks + // replace middle chunk(the search term) with actuall match + chunks[1] = chunks[1].replace('@'+myAC.dataSource.mentionQuery, + '@'+oData.nname+' '); + myAC.getInputEl().value = chunks.join('') + YUD.get(myAC.getInputEl()).focus(); // Y U NO WORK !? + } else { + //groups + myAC.getInputEl().value = oData.grname; + YUD.get('perm_new_member_type').value = 'users_group'; + } + }); + } + + // in this keybuffer we will gather current value of search ! + // since we need to get this just when someone does `@` then we do the + // search + ownerAC.dataSource.chunks = []; + ownerAC.dataSource.mentionQuery = null; + + ownerAC.get_mention = function(msg, max_pos) { + var org = msg; + var re = new RegExp('(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)$') + var chunks = []; + + + // cut first chunk until curret pos + var to_max = msg.substr(0, max_pos); + var at_pos = Math.max(0,to_max.lastIndexOf('@')-1); + var msg2 = to_max.substr(at_pos); + + chunks.push(org.substr(0,at_pos))// prefix chunk + chunks.push(msg2) // search chunk + chunks.push(org.substr(max_pos)) // postfix chunk + + // clean up msg2 for filtering and regex match + var msg2 = msg2.lstrip(' ').lstrip('\n'); + + if(re.test(msg2)){ + var unam = re.exec(msg2)[1]; + return [unam, chunks]; + } + return [null, null]; + }; + + if (ownerAC.textboxKeyUpEvent){ + ownerAC.textboxKeyUpEvent.subscribe(function(type, args){ + + var ac_obj = args[0]; + var currentMessage = args[1]; + var currentCaretPosition = args[0]._elTextbox.selectionStart; + + var unam = ownerAC.get_mention(currentMessage, currentCaretPosition); + var curr_search = null; + if(unam[0]){ + curr_search = unam[0]; + } + + ownerAC.dataSource.chunks = unam[1]; + ownerAC.dataSource.mentionQuery = curr_search; + + }) + } + return { + ownerDS: ownerDS, + ownerAC: ownerAC, + }; +} + + +var PullRequestAutoComplete = function (divid, cont, users_list, groups_list) { + var myUsers = users_list; + var myGroups = groups_list; + + // Define a custom search function for the DataSource of users + var matchUsers = function (sQuery) { + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myUsers.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + contact = myUsers[i]; + if (((contact.fname+"").toLowerCase().indexOf(query) > -1) || + ((contact.lname+"").toLowerCase().indexOf(query) > -1) || + ((contact.nname) && ((contact.nname).toLowerCase().indexOf(query) > -1))) { + matches[matches.length] = contact; + } + } + return matches; + }; + + // Define a custom search function for the DataSource of usersGroups + var matchGroups = function (sQuery) { + // Case insensitive matching + var query = sQuery.toLowerCase(); + var i = 0; + var l = myGroups.length; + var matches = []; + + // Match against each name of each contact + for (; i < l; i++) { + matched_group = myGroups[i]; + if (matched_group.grname.toLowerCase().indexOf(query) > -1) { + matches[matches.length] = matched_group; + } + } + return matches; + }; + + //match all + var matchAll = function (sQuery) { + u = matchUsers(sQuery); + return u + }; + + // DataScheme for owner + var ownerDS = new YAHOO.util.FunctionDataSource(matchUsers); + + ownerDS.responseSchema = { + fields: ["id", "fname", "lname", "nname", "gravatar_lnk"] + }; + + // Instantiate AutoComplete for mentions + var reviewerAC = new YAHOO.widget.AutoComplete(divid, cont, ownerDS); + reviewerAC.useShadow = false; + reviewerAC.resultTypeList = false; + reviewerAC.suppressInputUpdate = true; + reviewerAC.animVert = false; + reviewerAC.animHoriz = false; + reviewerAC.animSpeed = 0.1; + + // Helper highlight function for the formatter + var highlightMatch = function (full, snippet, matchindex) { + return full.substring(0, matchindex) + + "" + + full.substr(matchindex, snippet.length) + + "" + full.substring(matchindex + snippet.length); + }; + + // Custom formatter to highlight the matching letters + reviewerAC.formatResult = function (oResultData, sQuery, sResultMatch) { + var org_sQuery = sQuery; + if(this.dataSource.mentionQuery != null){ + sQuery = this.dataSource.mentionQuery; + } + + var query = sQuery.toLowerCase(); + var _gravatar = function(res, em, group){ + if (group !== undefined){ + em = '/images/icons/group.png' + } + tmpl = '
{1}
' + return tmpl.format(em,res) + } + if (oResultData.nname != undefined) { + var fname = oResultData.fname || ""; + var lname = oResultData.lname || ""; + var nname = oResultData.nname; + + // Guard against null value + var fnameMatchIndex = fname.toLowerCase().indexOf(query), + lnameMatchIndex = lname.toLowerCase().indexOf(query), + nnameMatchIndex = nname.toLowerCase().indexOf(query), + displayfname, displaylname, displaynname; + + if (fnameMatchIndex > -1) { + displayfname = highlightMatch(fname, query, fnameMatchIndex); + } else { + displayfname = fname; + } + + if (lnameMatchIndex > -1) { + displaylname = highlightMatch(lname, query, lnameMatchIndex); + } else { + displaylname = lname; + } + + if (nnameMatchIndex > -1) { + displaynname = "(" + highlightMatch(nname, query, nnameMatchIndex) + ")"; + } else { + displaynname = nname ? "(" + nname + ")" : ""; + } + + return _gravatar(displayfname + " " + displaylname + " " + displaynname, oResultData.gravatar_lnk); + } else { + return ''; + } + }; + + //members cache to catch duplicates + reviewerAC.dataSource.cache = []; + // hack into select event + if(reviewerAC.itemSelectEvent){ + reviewerAC.itemSelectEvent.subscribe(function (sType, aArgs) { + + var myAC = aArgs[0]; // reference back to the AC instance + var elLI = aArgs[1]; // reference to the selected LI element + var oData = aArgs[2]; // object literal of selected item's result data + var members = YUD.get('review_members'); + //fill the autocomplete with value + + if (oData.nname != undefined) { + if (myAC.dataSource.cache.indexOf(oData.id) != -1){ + return + } + + var tmpl = '
  • '+ + '
    '+ + '
    gravatar
    '+ + '
    {1}
    '+ + ''+ + ''+ + '
    '+ + '
  • ' + + var displayname = "{0} {1} ({2})".format(oData.fname,oData.lname,oData.nname); + var element = tmpl.format(oData.gravatar_lnk,displayname,oData.id); + members.innerHTML += element; + myAC.dataSource.cache.push(oData.id); + YUD.get('user').value = '' + } + }); + } + return { + ownerDS: ownerDS, + reviewerAC: reviewerAC, + }; +} + /** * QUICK REPO MENU @@ -1041,10 +1491,18 @@ var get_group_name = function(node){ return name } var get_date = function(node){ - var date_ = node.firstElementChild.innerHTML; + var date_ = YUD.getAttribute(node.firstElementChild,'date'); return date_ } +var get_age = function(node){ + return node +} + +var get_link = function(node){ + return node.firstElementChild.text; +} + var revisionSort = function(a, b, desc, field) { var a_ = fromHTML(a.getData(field)); @@ -1059,8 +1517,21 @@ var revisionSort = function(a, b, desc, return compState; }; var ageSort = function(a, b, desc, field) { - var a_ = a.getData(field); - var b_ = b.getData(field); + var a_ = fromHTML(a.getData(field)); + var b_ = fromHTML(b.getData(field)); + + // extract name from table + a_ = get_date(a_) + b_ = get_date(b_) + + var comp = YAHOO.util.Sort.compare; + var compState = comp(a_, b_, desc); + return compState; +}; + +var lastLoginSort = function(a, b, desc, field) { + var a_ = a.getData('last_login_raw') || 0; + var b_ = b.getData('last_login_raw') || 0; var comp = YAHOO.util.Sort.compare; var compState = comp(a_, b_, desc); @@ -1070,7 +1541,7 @@ var ageSort = function(a, b, desc, field var nameSort = function(a, b, desc, field) { var a_ = fromHTML(a.getData(field)); var b_ = fromHTML(b.getData(field)); - + // extract name from table a_ = get_name(a_) b_ = get_name(b_) @@ -1116,4 +1587,173 @@ var dateSort = function(a, b, desc, fiel var comp = YAHOO.util.Sort.compare; var compState = comp(a_, b_, desc); return compState; -}; \ No newline at end of file +}; + +var linkSort = function(a, b, desc, field) { + var a_ = fromHTML(a.getData(field)); + var b_ = fromHTML(a.getData(field)); + + // extract url text from string nodes + a_ = get_link(a_) + b_ = get_link(b_) + + var comp = YAHOO.util.Sort.compare; + var compState = comp(a_, b_, desc); + return compState; +} + +var addPermAction = function(_html, users_list, groups_list){ + var elmts = YUD.getElementsByClassName('last_new_member'); + var last_node = elmts[elmts.length-1]; + if (last_node){ + var next_id = (YUD.getElementsByClassName('new_members')).length; + _html = _html.format(next_id); + last_node.innerHTML = _html; + YUD.setStyle(last_node, 'display', ''); + YUD.removeClass(last_node, 'last_new_member'); + MembersAutoComplete("perm_new_member_name_"+next_id, + "perm_container_"+next_id, users_list, groups_list); + //create new last NODE + var el = document.createElement('tr'); + el.id = 'add_perm_input'; + YUD.addClass(el,'last_new_member'); + YUD.addClass(el,'new_members'); + YUD.insertAfter(el, last_node); + } +} + +/* Multi selectors */ + +var MultiSelectWidget = function(selected_id, available_id, form_id){ + + + //definition of containers ID's + var selected_container = selected_id; + var available_container = available_id; + + //temp container for selected storage. + var cache = new Array(); + var av_cache = new Array(); + var c = YUD.get(selected_container); + var ac = YUD.get(available_container); + + //get only selected options for further fullfilment + for(var i = 0;node =c.options[i];i++){ + if(node.selected){ + //push selected to my temp storage left overs :) + cache.push(node); + } + } + + //get all available options to cache + for(var i = 0;node =ac.options[i];i++){ + //push selected to my temp storage left overs :) + av_cache.push(node); + } + + //fill available only with those not in choosen + ac.options.length=0; + tmp_cache = new Array(); + + for(var i = 0;node = av_cache[i];i++){ + var add = true; + for(var i2 = 0;node_2 = cache[i2];i2++){ + if(node.value == node_2.value){ + add=false; + break; + } + } + if(add){ + tmp_cache.push(new Option(node.text, node.value, false, false)); + } + } + + for(var i = 0;node = tmp_cache[i];i++){ + ac.options[i] = node; + } + + function prompts_action_callback(e){ + + var choosen = YUD.get(selected_container); + var available = YUD.get(available_container); + + //get checked and unchecked options from field + function get_checked(from_field){ + //temp container for storage. + var sel_cache = new Array(); + var oth_cache = new Array(); + + for(var i = 0;node = from_field.options[i];i++){ + if(node.selected){ + //push selected fields :) + sel_cache.push(node); + } + else{ + oth_cache.push(node) + } + } + + return [sel_cache,oth_cache] + } + + //fill the field with given options + function fill_with(field,options){ + //clear firtst + field.options.length=0; + for(var i = 0;node = options[i];i++){ + field.options[i]=new Option(node.text, node.value, + false, false); + } + + } + //adds to current field + function add_to(field,options){ + for(var i = 0;node = options[i];i++){ + field.appendChild(new Option(node.text, node.value, + false, false)); + } + } + + // add action + if (this.id=='add_element'){ + var c = get_checked(available); + add_to(choosen,c[0]); + fill_with(available,c[1]); + } + // remove action + if (this.id=='remove_element'){ + var c = get_checked(choosen); + add_to(available,c[0]); + fill_with(choosen,c[1]); + } + // add all elements + if(this.id=='add_all_elements'){ + for(var i=0; node = available.options[i];i++){ + choosen.appendChild(new Option(node.text, + node.value, false, false)); + } + available.options.length = 0; + } + //remove all elements + if(this.id=='remove_all_elements'){ + for(var i=0; node = choosen.options[i];i++){ + available.appendChild(new Option(node.text, + node.value, false, false)); + } + choosen.options.length = 0; + } + + } + + YUE.addListener(['add_element','remove_element', + 'add_all_elements','remove_all_elements'],'click', + prompts_action_callback) + if (form_id !== undefined) { + YUE.addListener(form_id,'submit',function(){ + var choosen = YUD.get(selected_container); + for (var i = 0; i < choosen.options.length; i++) { + choosen.options[i].selected = 'selected'; + } + }); + } +} diff --git a/rhodecode/public/js/yui.2.9.js b/rhodecode/public/js/yui.2.9.js --- a/rhodecode/public/js/yui.2.9.js +++ b/rhodecode/public/js/yui.2.9.js @@ -1,150 +1,877 @@ +if("undefined"==typeof YAHOO||!YAHOO)var YAHOO={};YAHOO.namespace=function(){var a=arguments,c=null,b,d,f;for(b=0;b":">",'"':""","'":"'","/":"/","`":"`"},f=["toString","valueOf"],g={isArray:function(a){return"[object Array]"===c.toString.apply(a)},isBoolean:function(a){return"boolean"===typeof a},isFunction:function(a){return"function"===typeof a||"[object Function]"===c.toString.apply(a)},isNull:function(a){return null===a},isNumber:function(a){return"number"===typeof a&&isFinite(a)},isObject:function(b){return b&& +("object"===typeof b||a.isFunction(b))||!1},isString:function(a){return"string"===typeof a},isUndefined:function(a){return"undefined"===typeof a},_IEEnumFix:YAHOO.env.ua.ie?function(b,d){var e,i,k;for(e=0;e"'\/`]/g,function(a){return d[a]})},extend:function(b,d,e){if(!d||!b)throw Error("extend failed, please check that all dependencies are included.");var i=function(){},k;i.prototype= +d.prototype;b.prototype=new i;b.prototype.constructor=b;b.superclass=d.prototype;d.prototype.constructor==c.constructor&&(d.prototype.constructor=d);if(e){for(k in e)a.hasOwnProperty(e,k)&&(b.prototype[k]=e[k]);a._IEEnumFix(b.prototype,e)}},augmentObject:function(b,d){if(!d||!b)throw Error("Absorb failed, verify dependencies.");var e=arguments,i,k=e[2];if(k&&!0!==k)for(i=2;i "),a.isObject(b[e])?k.push(0k)break;f=b.indexOf("}",k);if(k+1>f)break;g=m=b.substring(k+1,f);n=null;c=g.indexOf(" ");-1f.webkit&&!l.finalpass&&!l.varName?(v=k(null,l.win,l.attributes),v.innerHTML='YAHOO.util.Get._finalize("'+d+'");',l.nodes.push(v),m.appendChild(v)):p(d);else{v=l.url[0];if(!v)return l.url.shift(),YAHOO.log("skipping empty url"),o(d);YAHOO.log("attempting to load "+ +v,"info","Get");l.timeout&&(l.timer=g.later(l.timeout,l,n,d));if("script"===l.type)x=k(v,h,l.attributes);else{x=l.attributes;var y={id:"yui__dyn_"+b++,type:"text/css",rel:"stylesheet",href:v};x&&g.augmentObject(y,x);x=i("link",y,h)}e(l.type,x,d,v,h,l.url.length);l.nodes.push(x);l.insertBefore?(m=q(l.insertBefore,d))&&m.parentNode.insertBefore(x,m):m.appendChild(x);YAHOO.log("Appending node: "+v,"info","Get");(f.webkit||f.gecko)&&"css"===l.type&&o(d,v)}},m=function(e,i,b){var k="q"+c++,b=b||{};if(0=== +c%YAHOO.util.Get.PURGE_THRESH&&!d){d=!0;var f,l;for(f in a)a.hasOwnProperty(f)&&(l=a[f],l.autopurge&&l.finished&&(h(l.tId),delete a[f]));d=!1}a[k]=g.merge(b,{tId:k,type:e,url:i,finished:!1,aborted:!1,nodes:[]});i=a[k];i.win=i.win||window;i.scope=i.scope||i.win;i.autopurge="autopurge"in i?i.autopurge:"script"===e?!0:!1;i.attributes=i.attributes||{};i.attributes.charset=b.charset||i.attributes.charset||"utf-8";g.later(0,i,o,k);return{tId:k}};e=function(e,i,b,k,d,l,c){var h=c||o,q,p,m,n,C,z;f.ie?i.onreadystatechange= +function(){q=this.readyState;if("loaded"===q||"complete"===q)YAHOO.log(b+" onload "+k,"info","Get"),i.onreadystatechange=null,h(b,k)}:f.webkit?"script"===e&&(420<=f.webkit?i.addEventListener("load",function(){YAHOO.log(b+" DOM2 onload "+k,"info","Get");h(b,k)}):(p=a[b],p.varName?(e=YAHOO.util.Get.POLL_FREQ,YAHOO.log("Polling for "+p.varName[0]),p.maxattempts=YAHOO.util.Get.TIMEOUT/e,p.attempts=0,p._cache=p.varName[0].split("."),p.timer=g.later(e,p,function(){m=this._cache;C=m.length;n=this.win;for(z= +0;zthis.maxattempts?(p.timer.cancel(),j(b,"Over retry limit, giving up")):YAHOO.log(m[z]+" failed, retrying");return}YAHOO.log("Safari poll complete");p.timer.cancel();h(b,k)},null,!0)):g.later(YAHOO.util.Get.POLL_FREQ,null,h,[b,k]))):i.onload=function(){YAHOO.log(b+" onload "+k,"info","Get");h(b,k)}};j=function(e,i){YAHOO.log("get failure: "+i,"warn","Get");var b=a[e],k;b.onFailure&&(k=b.scope||b.win,b.onFailure.call(k,l(b,i)))};h=function(e){if(a[e]){var i= +a[e],b=i.nodes,k=b.length,d=i.win.document.getElementsByTagName("head")[0],f,l;if(i.insertBefore&&(e=q(i.insertBefore,e)))d=e.parentNode;for(e=0;e=b.rollup))break}else for(a=0;a=b.rollup))break;j&&(r=g[e]= +!0,this.getRequires(b))}if(!r)break}},_reduce:function(){var e,a,b,d=this.required;for(e in d)if(e in this.loaded)delete d[e];else if(b=this.parseSkin(e)){if(!b.module){var f=this.SKIN_PREFIX+b.skin;for(a in d)h.hasOwnProperty(d,a)&&(b=this.moduleInfo[a],(!b||!b.ext)&&(a!==e&&-1 +g?YAHOO.util.Get.script(e._filter(d),{data:e._loading,onSuccess:t,onFailure:e._onFailure,onTimeout:e._onTimeout,insertBefore:e.insertBefore,charset:e.charset,timeout:e.timeout,scope:e}):this.loadNext()};f.length>g?YAHOO.util.Get.css(this._filter(f),{data:this._loading,onSuccess:a,onFailure:this._onFailure,onTimeout:this._onTimeout,insertBefore:this.insertBefore,charset:this.charset,timeout:this.timeout,scope:e}):a()}else this.loadNext(this._loading)},insert:function(e,a){this.calculate(e);this._loading= +!0;this.loadType=a;if(this.combine)return this._combine();if(a)this.loadNext();else{var b=this;this._internalCallback=function(){b._internalCallback=null;b.insert(null,"js")};this.insert(null,"css")}},sandbox:function(e,a){var b=this,d=function(e){var a=e.argument[2];b._scriptText[e.argument[0]]=e.responseText;b.onProgress&&b.onProgress.call(b.scope,{name:a,scriptText:e.responseText,xhrResponse:e,data:b.data});b._loadCount++;b._loadCount>=b._stopCount&&(e="\nreturn "+(b.varName||"YAHOO")+";\n})();", +e=eval("(function() {\n"+b._scriptText.join("\n")+e),b._pushEvents(e),e?b.onSuccess.call(b.scope,{reference:e,data:b.data}):b._onFailure.call(b.varName+" reference failure"))},f=function(e){b.onFailure.call(b.scope,{msg:"XHR failure",xhrResponse:e,data:b.data})};b._config(e);if(!b.onSuccess)throw Error("You must supply an onSuccess handler for your sandbox");b._sandbox=!0;if(!a||"js"!==a)b._internalCallback=function(){b._internalCallback=null;b.sandbox(null,"js")},b.insert(null,"css");else if(j.Connect){b._scriptText= +[];b._loadCount=0;b._stopCount=b.sorted.length;b._xhr=[];b.calculate();var c=b.sorted,g=c.length,h,r,s;for(h=0;he.ua.webkit&&"js"===g.type&&!g.varName)&&(d=null,this._useYahooListener=!0);c(h,{data:f[a],onSuccess:d,onFailure:this._onFailure,onTimeout:this._onTimeout,insertBefore:this.insertBefore,charset:this.charset,timeout:this.timeout,varName:g.varName,scope:b});return}}this._loading=null;this._internalCallback?(f=this._internalCallback,this._internalCallback= +null,f.call(this)):this.onSuccess&&(this._pushEvents(),this.onSuccess.call(this.scope,{data:this.data}))}},_pushEvents:function(e){e=e||YAHOO;e.util&&e.util.Event&&e.util.Event._load()},_filter:function(e){var a=this.filter;return a?e.replace(RegExp(a.searchExp,"g"),a.replaceStr):e},_url:function(e){return this._filter((this.base||"")+e)}}})();YAHOO.register("yuiloader",YAHOO.util.YUILoader,{version:"2.9.0",build:"2800"}); +(function(){YAHOO.env._id_counter=YAHOO.env._id_counter||0;var a=YAHOO.util,c=YAHOO.lang,b=YAHOO.env.ua,d=YAHOO.lang.trim,f={},g={},j=/^t(?:able|d|h)$/i,h=/color$/i,e=window.document,i=e.documentElement,k=b.opera,l=b.webkit,q=b.gecko,p=b.ie;a.Dom={CUSTOM_ATTRIBUTES:!i.hasAttribute?{"for":"htmlFor","class":"className"}:{htmlFor:"for",className:"class"},DOT_ATTRIBUTES:{checked:!0},get:function(i){var b,d,f,k;b=null;if(i){if("string"==typeof i||"number"==typeof i){b=i+"";f=(i=e.getElementById(i))?i.attributes: +null;if(i&&f&&f.id&&f.id.value===b)return i;if(i&&e.all&&(i=null,(d=e.all[b])&&d.length)){f=0;for(k=d.length;f=this.left&&a.right<=this.right&&a.top>=this.top&&a.bottom<=this.bottom};YAHOO.util.Region.prototype.getArea=function(){return(this.bottom-this.top)*(this.right-this.left)}; +YAHOO.util.Region.prototype.intersect=function(a){var c=Math.max(this.top,a.top),b=Math.min(this.right,a.right),d=Math.min(this.bottom,a.bottom),a=Math.max(this.left,a.left);return d>=c&&b>=a?new YAHOO.util.Region(c,b,d,a):null};YAHOO.util.Region.prototype.union=function(a){var c=Math.min(this.top,a.top),b=Math.max(this.right,a.right),d=Math.max(this.bottom,a.bottom),a=Math.min(this.left,a.left);return new YAHOO.util.Region(c,b,d,a)}; +YAHOO.util.Region.prototype.toString=function(){return"Region {top: "+this.top+", right: "+this.right+", bottom: "+this.bottom+", left: "+this.left+", height: "+this.height+", width: "+this.width+"}"};YAHOO.util.Region.getRegion=function(a){var c=YAHOO.util.Dom.getXY(a);return new YAHOO.util.Region(c[1],c[0]+a.offsetWidth,c[1]+a.offsetHeight,c[0])};YAHOO.util.Point=function(a,c){YAHOO.lang.isArray(a)&&(c=a[1],a=a[0]);YAHOO.util.Point.superclass.constructor.call(this,c,a,c,a)}; +YAHOO.extend(YAHOO.util.Point,YAHOO.util.Region); +(function(){var a=YAHOO.util,c=/^width|height$/,b=/^(\d[.\d]*)+(em|ex|px|gd|rem|vw|vh|vm|ch|mm|cm|in|pt|pc|deg|rad|ms|s|hz|khz|%){1}?/i,d={get:function(d,f){var c="",c=d.currentStyle[f];return c="opacity"===f?a.Dom.getStyle(d,"opacity"):!c||c.indexOf&&-1d&&(e=d-(a[i]-d)),a.style[b]="auto")):(!a.style[f]&&!a.style[b]&&(a.style[b]=d),e=a.style[f]);return e+"px"},getBorderWidth:function(a,b){var d=null;a.currentStyle.hasLayout||(a.style.zoom=1);switch(b){case "borderTopWidth":d=a.clientTop;break;case "borderBottomWidth":d=a.offsetHeight-a.clientHeight-a.clientTop;break;case "borderLeftWidth":d=a.clientLeft;break;case "borderRightWidth":d=a.offsetWidth-a.clientWidth-a.clientLeft}return d+"px"},getPixel:function(a, +b){var d=null,e=a.currentStyle.right;a.style.right=a.currentStyle[b];d=a.style.pixelRight;a.style.right=e;return d+"px"},getMargin:function(b,d){return"auto"==b.currentStyle[d]?"0px":a.Dom.IE.ComputedStyle.getPixel(b,d)},getVisibility:function(a,b){for(var d;(d=a.currentStyle)&&"inherit"==d[b];)a=a.parentNode;return d?d[b]:"visible"},getColor:function(b,d){return a.Dom.Color.toRGB(b.currentStyle[d])||"transparent"},getBorderColor:function(b,d){var f=b.currentStyle;return a.Dom.Color.toRGB(a.Dom.Color.toHex(f[d]|| +f.color))}},f={};f.top=f.right=f.bottom=f.left=f.width=f.height=d.getOffset;f.color=d.getColor;f.borderTopWidth=f.borderRightWidth=f.borderBottomWidth=f.borderLeftWidth=d.getBorderWidth;f.marginTop=f.marginRight=f.marginBottom=f.marginLeft=d.getMargin;f.visibility=d.getVisibility;f.borderColor=f.borderTopColor=f.borderRightColor=f.borderBottomColor=f.borderLeftColor=d.getBorderColor;a.Dom.IE_COMPUTED=f;a.Dom.IE_ComputedStyle=d})(); +(function(){var a=parseInt,c=RegExp,b=YAHOO.util;b.Dom.Color={KEYWORDS:{black:"000",silver:"c0c0c0",gray:"808080",white:"fff",maroon:"800000",red:"f00",purple:"800080",fuchsia:"f0f",green:"008000",lime:"0f0",olive:"808000",yellow:"ff0",navy:"000080",blue:"00f",teal:"008080",aqua:"0ff"},re_RGB:/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,re_hex:/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,re_hex3:/([0-9A-F])/gi,toRGB:function(d){b.Dom.Color.re_RGB.test(d)||(d=b.Dom.Color.toHex(d));b.Dom.Color.re_hex.exec(d)&& +(d="rgb("+[a(c.$1,16),a(c.$2,16),a(c.$3,16)].join(", ")+")");return d},toHex:function(a){a=b.Dom.Color.KEYWORDS[a]||a;if(b.Dom.Color.re_RGB.exec(a)){for(var a=[Number(c.$1).toString(16),Number(c.$2).toString(16),Number(c.$3).toString(16)],f=0;fa[f].length&&(a[f]="0"+a[f]);a=a.join("")}6>a.length&&(a=a.replace(b.Dom.Color.re_hex3,"$1$1"));"transparent"!==a&&0>a.indexOf("#")&&(a="#"+a);return a.toUpperCase()}}})();YAHOO.register("dom",YAHOO.util.Dom,{version:"2.9.0",build:"2800"}); +YAHOO.util.CustomEvent=function(a,c,b,d,f){this.type=a;this.scope=c||window;this.silent=b;this.fireOnce=f;this.fired=!1;this.firedWith=null;this.signature=d||YAHOO.util.CustomEvent.LIST;this.subscribers=[];"_YUICEOnSubscribe"!==a&&(this.subscribeEvent=new YAHOO.util.CustomEvent("_YUICEOnSubscribe",this,!0));this.lastError=null};YAHOO.util.CustomEvent.LIST=0;YAHOO.util.CustomEvent.FLAT=1; +YAHOO.util.CustomEvent.prototype={subscribe:function(a,c,b){if(!a)throw Error("Invalid callback for subscriber to '"+this.type+"'");this.subscribeEvent&&this.subscribeEvent.fire(a,c,b);a=new YAHOO.util.Subscriber(a,c,b);this.fireOnce&&this.fired?this.notify(a,this.firedWith):this.subscribers.push(a)},unsubscribe:function(a,c){if(!a)return this.unsubscribeAll();for(var b=!1,d=0,f=this.subscribers.length;da.webkit?a._dri=setInterval(function(){var b=document.readyState;if("loaded"==b||"complete"==b)clearInterval(a._dri),a._dri=null,a._ready()},a.POLL_INTERVAL):a._simpleAdd(document,"DOMContentLoaded",a._ready);a._simpleAdd(window,"load",a._load);a._simpleAdd(window,"unload",a._unload);a._tryPreloadAttach()}());YAHOO.util.EventProvider=function(){}; +YAHOO.util.EventProvider.prototype={__yui_events:null,__yui_subscribers:null,subscribe:function(a,c,b,d){this.__yui_events=this.__yui_events||{};var f=this.__yui_events[a];if(f)f.subscribe(c,b,d);else{f=this.__yui_subscribers=this.__yui_subscribers||{};f[a]||(f[a]=[]);f[a].push({fn:c,obj:b,overrideContext:d})}},unsubscribe:function(a,c,b){var d=this.__yui_events=this.__yui_events||{};if(a){if(d=d[a])return d.unsubscribe(c,b)}else{var a=true,f;for(f in d)YAHOO.lang.hasOwnProperty(d,f)&&(a=a&&d[f].unsubscribe(c, +b));return a}return false},unsubscribeAll:function(a){return this.unsubscribe(a)},createEvent:function(a,c){this.__yui_events=this.__yui_events||{};var b=c||{},d=this.__yui_events,f;if(!d[a]){f=new YAHOO.util.CustomEvent(a,b.scope||this,b.silent,YAHOO.util.CustomEvent.FLAT,b.fireOnce);d[a]=f;b.onSubscribeCallback&&f.subscribeEvent.subscribe(b.onSubscribeCallback);this.__yui_subscribers=this.__yui_subscribers||{};if(b=this.__yui_subscribers[a])for(var g=0;g= +200&&d<300||d===1223||g){b=a.xdr?a.r:this.createResponseObject(a,f);c&&c.success&&(c.scope?c.success.apply(c.scope,[b]):c.success(b));this.successEvent.fire(b);a.successEvent&&a.successEvent.fire(b)}else{switch(d){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:b=this.createExceptionObject(a.tId,f,b?b:false);c&&c.failure&&(c.scope?c.failure.apply(c.scope,[b]):c.failure(b));break;default:b=a.xdr?a.response:this.createResponseObject(a,f);c&&c.failure&&(c.scope?c.failure.apply(c.scope, +[b]):c.failure(b))}this.failureEvent.fire(b);a.failureEvent&&a.failureEvent.fire(b)}this.releaseObject(a)},createResponseObject:function(a,c){var b={},d={},f,g,j,h;try{g=a.conn.getAllResponseHeaders();j=g.split("\n");for(f=0;f',b=document.createElement("div");document.body.appendChild(b);b.innerHTML=a}var c=YAHOO.util.Connect,b={};c.xdr=function(a,f,c,j,h){b[parseInt(a.tId)]={o:a,c:j};if(h){j.method=f;j.data=h}a.conn.send(c,j,a.tId)};c.swf=a;c.transport=function(b){a(b);c._transport=document.getElementById("YUIConnectionSwf")}; +c.xdrReadyEvent=new YAHOO.util.CustomEvent("xdrReady");c.xdrReady=function(){c.xdrReadyEvent.fire()};c.handleXdrResponse=function(a){var f=b[a.tId].o,g=b[a.tId].c;if(a.statusText==="xdr:start"){if(f){c.startEvent.fire(f,g.argument);f.startEvent&&f.startEvent.fire(f,g.argument)}}else{a.responseText=decodeURI(a.responseText);f.r=a;if(g.argument)f.r.argument=g.argument;this.handleTransactionResponse(f,g,a.statusText==="xdr:abort"?true:false);delete b[a.tId]}}})(); +(function(){var a=YAHOO.util.Connect,c=YAHOO.util.Event,b=document.documentMode?document.documentMode:false;a._isFileUpload=false;a._formNode=null;a._sFormData=null;a._submitElementValue=null;a.uploadEvent=new YAHOO.util.CustomEvent("upload");a._hasSubmitListener=function(){if(c){c.addListener(document,"click",function(b){var b=c.getTarget(b),f=b.nodeName.toLowerCase();if((f==="input"||f==="button")&&b.type&&b.type.toLowerCase()=="submit")a._submitElementValue=encodeURIComponent(b.name)+"="+encodeURIComponent(b.value)}); +return true}return false}();a.setForm=function(a,b,c){var j,h=false,e=[],i=0,k,l,q,p;this.resetFormState();if(typeof a=="string")a=document.getElementById(a)||document.forms[a];else if(typeof a!="object")return;if(b){this.createFrame(c?c:null);this._isFileUpload=this._isFormSubmit=true;this._formNode=a}else{k=0;for(l=a.elements.length;k +-1){p=b.options[b.selectedIndex];e[i++]=c+encodeURIComponent(p.attributes.value&&p.attributes.value.specified?p.value:p.text)}break;case "select-multiple":if(b.selectedIndex>-1){j=b.selectedIndex;for(q=b.options.length;j'); +if(typeof a=="boolean")c.src="javascript:false"}else{c=document.createElement("iframe");c.id=f;c.name=f}c.style.position="absolute";c.style.top="-1000px";c.style.left="-1000px";document.body.appendChild(c)};a.appendPostData=function(a){var b=[],a=a.split("&"),c,j;for(c=0;c=8?true:false,k=this,l=f&&f.argument?f.argument:null,q,p,n,o,m;o={action:this._formNode.getAttribute("action"),method:this._formNode.getAttribute("method"),target:this._formNode.getAttribute("target")};this._formNode.setAttribute("action",g);this._formNode.setAttribute("method","POST");this._formNode.setAttribute("target",h);YAHOO.env.ua.ie&&!i?this._formNode.setAttribute("encoding","multipart/form-data"):this._formNode.setAttribute("enctype", +"multipart/form-data");j&&(q=this.appendPostData(j));this._formNode.submit();this.startEvent.fire(a,l);a.startEvent&&a.startEvent.fire(a,l);f&&f.timeout&&(this._timeOut[a.tId]=window.setTimeout(function(){k.abort(a,f,true)},f.timeout));if(q&&q.length>0)for(g=0;g0?d:0);b in c&&!("style"in c&&b in c.style)?c[b]=d:a.Dom.setStyle(c,b,d+f)},getAttribute:function(b){var d=this.getEl(),f=a.Dom.getStyle(d,b);if(f!=="auto"&&!this.patterns.offsetUnit.test(f))return parseFloat(f);var c=this.patterns.offsetAttribute.exec(b)||[],j=!!c[3],h=!!c[2];"style"in d?f=h||a.Dom.getStyle(d,"position")=="absolute"&&j?d["offset"+c[0].charAt(0).toUpperCase()+c[0].substr(1)]:0:b in d&&(f=d[b]);return f},getDefaultUnit:function(a){return this.patterns.defaultUnit.test(a)? +"px":""},setRuntimeAttribute:function(a){var d,f,c=this.attributes;this.runtimeAttributes[a]={};var j=function(e){return typeof e!=="undefined"};if(!j(c[a].to)&&!j(c[a].by))return false;d=j(c[a].from)?c[a].from:this.getAttribute(a);if(j(c[a].to))f=c[a].to;else if(j(c[a].by))if(d.constructor==Array){f=[];for(var h=0,e=d.length;h0&&isFinite(m)){d.currentFrame+m>=f&&(m=f-(g+1));d.currentFrame=d.currentFrame+m}}b._onTween.fire()}else YAHOO.util.AnimMgr.stop(b,e)}};var h=function(e){for(var a=0,b=c.length;a0&&!(i[0]instanceof Array))i=[i];else{var p=[];l=0;for(q=i.length;l0&&(this.runtimeAttributes[a]=this.runtimeAttributes[a].concat(i)); +this.runtimeAttributes[a][this.runtimeAttributes[a].length]=k}else b.setRuntimeAttribute.call(this,a)};var f=function(a,b){var e=c.Dom.getXY(this.getEl());return a=[a[0]-e[0]+b[0],a[1]-e[1]+b[1]]},g=function(a){return typeof a!=="undefined"};c.Motion=a})(); +(function(){var a=function(b,d,c,h){b&&a.superclass.constructor.call(this,b,d,c,h)};a.NAME="Scroll";var c=YAHOO.util;YAHOO.extend(a,c.ColorAnim);var b=a.superclass,d=a.prototype;d.doMethod=function(a,d,c){var h=null;return h=a=="scroll"?[this.method(this.currentFrame,d[0],c[0]-d[0],this.totalFrames),this.method(this.currentFrame,d[1],c[1]-d[1],this.totalFrames)]:b.doMethod.call(this,a,d,c)};d.getAttribute=function(a){var d=null,d=this.getEl();return d=a=="scroll"?[d.scrollLeft,d.scrollTop]:b.getAttribute.call(this, +a)};d.setAttribute=function(a,d,c){var h=this.getEl();if(a=="scroll"){h.scrollLeft=d[0];h.scrollTop=d[1]}else b.setAttribute.call(this,a,d,c)};c.Scroll=a})();YAHOO.register("animation",YAHOO.util.Anim,{version:"2.9.0",build:"2800"}); +YAHOO.util.DragDropMgr||(YAHOO.util.DragDropMgr=function(){var a=YAHOO.util.Event,c=YAHOO.util.Dom;return{useShim:false,_shimActive:false,_shimState:false,_debugShim:false,_createShim:function(){var b=document.createElement("div");b.id="yui-ddm-shim";document.body.firstChild?document.body.insertBefore(b,document.body.firstChild):document.body.appendChild(b);b.style.display="none";b.style.backgroundColor="red";b.style.position="absolute";b.style.zIndex="99999";c.setStyle(b,"opacity","0");this._shim= +b;a.on(b,"mouseup",this.handleMouseUp,this,true);a.on(b,"mousemove",this.handleMouseMove,this,true);a.on(window,"scroll",this._sizeShim,this,true)},_sizeShim:function(){if(this._shimActive){var a=this._shim;a.style.height=c.getDocumentHeight()+"px";a.style.width=c.getDocumentWidth()+"px";a.style.top="0";a.style.left="0"}},_activateShim:function(){if(this.useShim){this._shim||this._createShim();this._shimActive=true;var a=this._shim,d="0";this._debugShim&&(d=".5");c.setStyle(a,"opacity",d);this._sizeShim(); +a.style.display="block"}},_deactivateShim:function(){this._shim.style.display="none";this._shimActive=false},_shim:null,ids:{},handleIds:{},dragCurrent:null,dragOvers:{},deltaX:0,deltaY:0,preventDefault:true,stopPropagation:true,initialized:false,locked:false,interactionInfo:null,init:function(){this.initialized=true},POINT:0,INTERSECT:1,STRICT_INTERSECT:2,mode:0,_execOnAll:function(a,d){for(var f in this.ids)for(var c in this.ids[f]){var j=this.ids[f][c];this.isTypeOfDD(j)&&j[a].apply(j,d)}},_onLoad:function(){this.init(); +a.on(document,"mouseup",this.handleMouseUp,this,true);a.on(document,"mousemove",this.handleMouseMove,this,true);a.on(window,"unload",this._onUnload,this,true);a.on(window,"resize",this._onResize,this,true)},_onResize:function(){this._execOnAll("resetConstraints",[])},lock:function(){this.locked=true},unlock:function(){this.locked=false},isLocked:function(){return this.locked},locationCache:{},useCache:true,clickPixelThresh:3,clickTimeThresh:1E3,dragThreshMet:false,clickTimeout:null,startX:0,startY:0, +fromTimeout:false,regDragDrop:function(a,d){this.initialized||this.init();this.ids[d]||(this.ids[d]={});this.ids[d][a.id]=a},removeDDFromGroup:function(a,d){this.ids[d]||(this.ids[d]={});var f=this.ids[d];f&&f[a.id]&&delete f[a.id]},_remove:function(a){for(var d in a.groups)if(d){var f=this.ids[d];f&&f[a.id]&&delete f[a.id]}delete this.handleIds[a.id]},regHandle:function(a,d){this.handleIds[a]||(this.handleIds[a]={});this.handleIds[a][d]=d},isDragDrop:function(a){return this.getDDById(a)?true:false}, +getRelated:function(a,d){var f=[],c;for(c in a.groups)for(var j in this.ids[c]){var h=this.ids[c][j];if(this.isTypeOfDD(h)&&(!d||h.isTarget))f[f.length]=h}return f},isLegalTarget:function(a,d){for(var f=this.getRelated(a,true),c=0,j=f.length;cthis.clickPixelThresh||c>this.clickPixelThresh)&&this.startDrag(this.startX, +this.startY)}if(this.dragThreshMet){if(d&&d.events.b4Drag){d.b4Drag(a);d.fireEvent("b4DragEvent",{e:a})}if(d&&d.events.drag){d.onDrag(a);d.fireEvent("dragEvent",{e:a})}d&&this.fireEvents(a,false)}this.stopEvent(a)}},fireEvents:function(a,d){var f=this.dragCurrent;if(f&&!f.isLocked()&&!f.dragOnly){var c=YAHOO.util.Event.getPageX(a),j=YAHOO.util.Event.getPageY(a),h=new YAHOO.util.Point(c,j),j=f.getTargetCoord(h.x,h.y),e=f.getDragEl(),c=["out","over","drop","enter"],i=new YAHOO.util.Region(j.y,j.x+e.offsetWidth, +j.y+e.offsetHeight,j.x),k=[],l={},j={},e=[],q={outEvts:[],overEvts:[],dropEvts:[],enterEvts:[]},p;for(p in this.dragOvers){var n=this.dragOvers[p];if(this.isTypeOfDD(n)){this.isOverTarget(h,n,this.mode,i)||q.outEvts.push(n);k[p]=true;delete this.dragOvers[p]}}for(var o in f.groups)if("string"==typeof o)for(p in this.ids[o]){n=this.ids[o][p];if(this.isTypeOfDD(n)&&n.isTarget&&(!n.isLocked()&&n!=f)&&this.isOverTarget(h,n,this.mode,i)){l[o]=true;if(d)q.dropEvts.push(n);else{k[n.id]?q.overEvts.push(n): +q.enterEvts.push(n);this.dragOvers[n.id]=n}}}this.interactionInfo={out:q.outEvts,enter:q.enterEvts,over:q.overEvts,drop:q.dropEvts,point:h,draggedRegion:i,sourceRegion:this.locationCache[f.id],validDrop:d};for(var m in l)e.push(m);if(d&&!q.dropEvts.length){this.interactionInfo.validDrop=false;if(f.events.invalidDrop){f.onInvalidDrop(a);f.fireEvent("invalidDropEvent",{e:a})}}for(p=0;p2E3)){setTimeout(a._addListeners,10);if(document&&document.body)a._timeoutCount=a._timeoutCount+1}},handleWasClicked:function(a,d){if(this.isHandle(d,a.id))return true;for(var f=a.parentNode;f;){if(this.isHandle(d, +f.id))return true;f=f.parentNode}return false}}}(),YAHOO.util.DDM=YAHOO.util.DragDropMgr,YAHOO.util.DDM._addListeners()); +(function(){var a=YAHOO.util.Event,c=YAHOO.util.Dom;YAHOO.util.DragDrop=function(a,d,f){a&&this.init(a,d,f)};YAHOO.util.DragDrop.prototype={events:null,on:function(){this.subscribe.apply(this,arguments)},id:null,config:null,dragElId:null,handleElId:null,invalidHandleTypes:null,invalidHandleIds:null,invalidHandleClasses:null,startPageX:0,startPageY:0,groups:null,locked:false,lock:function(){this.locked=true},unlock:function(){this.locked=false},isTarget:true,padding:null,dragOnly:false,useShim:false, +_domRef:null,__ygDragDrop:true,constrainX:false,constrainY:false,minX:0,maxX:0,minY:0,maxY:0,deltaX:0,deltaY:0,maintainOffset:false,xTicks:null,yTicks:null,primaryButtonOnly:true,available:false,hasOuterHandles:false,cursorIsOver:false,overlap:null,b4StartDrag:function(){},startDrag:function(){},b4Drag:function(){},onDrag:function(){},onDragEnter:function(){},b4DragOver:function(){},onDragOver:function(){},b4DragOut:function(){},onDragOut:function(){},b4DragDrop:function(){},onDragDrop:function(){}, +onInvalidDrop:function(){},b4EndDrag:function(){},endDrag:function(){},b4MouseDown:function(){},onMouseDown:function(){},onMouseUp:function(){},onAvailable:function(){},getEl:function(){if(!this._domRef)this._domRef=c.get(this.id);return this._domRef},getDragEl:function(){return c.get(this.dragElId)},init:function(b,d,f){this.initTarget(b,d,f);a.on(this._domRef||this.id,"mousedown",this.handleMouseDown,this,true);for(var c in this.events)this.createEvent(c+"Event")},initTarget:function(b,d,f){this.config= +f||{};this.events={};this.DDM=YAHOO.util.DDM;this.groups={};if(typeof b!=="string"){this._domRef=b;b=c.generateId(b)}this.id=b;this.addToGroup(d?d:"default");this.handleElId=b;a.onAvailable(b,this.handleOnAvailable,this,true);this.setDragElId(b);this.invalidHandleTypes={A:"A"};this.invalidHandleIds={};this.invalidHandleClasses=[];this.applyConfig()},applyConfig:function(){this.events={mouseDown:true,b4MouseDown:true,mouseUp:true,b4StartDrag:true,startDrag:true,b4EndDrag:true,endDrag:true,drag:true, +b4Drag:true,invalidDrop:true,b4DragOut:true,dragOut:true,dragEnter:true,b4DragOver:true,dragOver:true,b4DragDrop:true,dragDrop:true};if(this.config.events)for(var a in this.config.events)this.config.events[a]===false&&(this.events[a]=false);this.padding=this.config.padding||[0,0,0,0];this.isTarget=this.config.isTarget!==false;this.maintainOffset=this.config.maintainOffset;this.primaryButtonOnly=this.config.primaryButtonOnly!==false;this.dragOnly=this.config.dragOnly===true?true:false;this.useShim= +this.config.useShim===true?true:false},handleOnAvailable:function(){this.available=true;this.resetConstraints();this.onAvailable()},setPadding:function(a,d,f,c){this.padding=!d&&0!==d?[a,a,a,a]:!f&&0!==f?[a,d,a,d]:[a,d,f,c]},setInitPosition:function(a,d){var f=this.getEl();if(this.DDM.verifyEl(f)){var g=a||0,j=d||0,f=c.getXY(f);this.initPageX=f[0]-g;this.initPageY=f[1]-j;this.lastPageX=f[0];this.lastPageY=f[1];this.setStartPosition(f)}},setStartPosition:function(a){a=a||c.getXY(this.getEl());this.deltaSetXY= +null;this.startPageX=a[0];this.startPageY=a[1]},addToGroup:function(a){this.groups[a]=true;this.DDM.regDragDrop(this,a)},removeFromGroup:function(a){this.groups[a]&&delete this.groups[a];this.DDM.removeDDFromGroup(this,a)},setDragElId:function(a){this.dragElId=a},setHandleElId:function(a){typeof a!=="string"&&(a=c.generateId(a));this.handleElId=a;this.DDM.regHandle(this.id,a)},setOuterHandleElId:function(b){typeof b!=="string"&&(b=c.generateId(b));a.on(b,"mousedown",this.handleMouseDown,this,true); +this.setHandleElId(b);this.hasOuterHandles=true},unreg:function(){a.removeListener(this.id,"mousedown",this.handleMouseDown);this._domRef=null;this.DDM._remove(this)},isLocked:function(){return this.DDM.isLocked()||this.locked},handleMouseDown:function(b){var d=b.which||b.button;if(!(this.primaryButtonOnly&&d>1)&&!this.isLocked()){var d=this.b4MouseDown(b),f=true;this.events.b4MouseDown&&(f=this.fireEvent("b4MouseDownEvent",b));var c=this.onMouseDown(b),j=true;this.events.mouseDown&&(j=c===false? +false:this.fireEvent("mouseDownEvent",b));if(!(d===false||c===false||f===false||j===false)){this.DDM.refreshCache(this.groups);d=new YAHOO.util.Point(a.getPageX(b),a.getPageY(b));if((this.hasOuterHandles||this.DDM.isOverTarget(d,this))&&this.clickValidator(b)){this.setStartPosition();this.DDM.handleMouseDown(b,this);this.DDM.stopEvent(b)}}}},clickValidator:function(a){a=YAHOO.util.Event.getTarget(a);return this.isValidHandleChild(a)&&(this.id==this.handleElId||this.DDM.handleWasClicked(a,this.id))}, +getTargetCoord:function(a,d){var f=a-this.deltaX,c=d-this.deltaY;if(this.constrainX){if(fthis.maxX)f=this.maxX}if(this.constrainY){if(cthis.maxY)c=this.maxY}f=this.getTick(f,this.xTicks);c=this.getTick(c,this.yTicks);return{x:f,y:c}},addInvalidHandleType:function(a){a=a.toUpperCase();this.invalidHandleTypes[a]=a},addInvalidHandleId:function(a){typeof a!=="string"&&(a=c.generateId(a));this.invalidHandleIds[a]=a},addInvalidHandleClass:function(a){this.invalidHandleClasses.push(a)}, +removeInvalidHandleType:function(a){delete this.invalidHandleTypes[a.toUpperCase()]},removeInvalidHandleId:function(a){typeof a!=="string"&&(a=c.generateId(a));delete this.invalidHandleIds[a]},removeInvalidHandleClass:function(a){for(var d=0,f=this.invalidHandleClasses.length;d=this.minX;c=c-d)if(!f[c]){this.xTicks[this.xTicks.length]=c;f[c]=true}for(c=this.initPageX;c<=this.maxX;c=c+d)if(!f[c]){this.xTicks[this.xTicks.length]=c;f[c]=true}this.xTicks.sort(this.DDM.numericSort)},setYTicks:function(a,d){this.yTicks=[];this.yTickSize=d;for(var f={},c=this.initPageY;c>=this.minY;c= +c-d)if(!f[c]){this.yTicks[this.yTicks.length]=c;f[c]=true}for(c=this.initPageY;c<=this.maxY;c=c+d)if(!f[c]){this.yTicks[this.yTicks.length]=c;f[c]=true}this.yTicks.sort(this.DDM.numericSort)},setXConstraint:function(a,d,f){this.leftConstraint=parseInt(a,10);this.rightConstraint=parseInt(d,10);this.minX=this.initPageX-this.leftConstraint;this.maxX=this.initPageX+this.rightConstraint;f&&this.setXTicks(this.initPageX,f);this.constrainX=true},clearConstraints:function(){this.constrainY=this.constrainX= +false;this.clearTicks()},clearTicks:function(){this.yTicks=this.xTicks=null;this.yTickSize=this.xTickSize=0},setYConstraint:function(a,d,f){this.topConstraint=parseInt(a,10);this.bottomConstraint=parseInt(d,10);this.minY=this.initPageY-this.topConstraint;this.maxY=this.initPageY+this.bottomConstraint;f&&this.setYTicks(this.initPageY,f);this.constrainY=true},resetConstraints:function(){this.initPageX||this.initPageX===0?this.setInitPosition(this.maintainOffset?this.lastPageX-this.initPageX:0,this.maintainOffset? +this.lastPageY-this.initPageY:0):this.setInitPosition();this.constrainX&&this.setXConstraint(this.leftConstraint,this.rightConstraint,this.xTickSize);this.constrainY&&this.setYConstraint(this.topConstraint,this.bottomConstraint,this.yTickSize)},getTick:function(a,d){if(d){if(d[0]>=a)return d[0];for(var f=0,c=d.length;f=a)return d[j]-a>a-d[f]?d[f]:d[j]}return d[d.length-1]}return a},toString:function(){return"DragDrop "+this.id}};YAHOO.augment(YAHOO.util.DragDrop,YAHOO.util.EventProvider)})(); +YAHOO.util.DD=function(a,c,b){a&&this.init(a,c,b)}; +YAHOO.extend(YAHOO.util.DD,YAHOO.util.DragDrop,{scroll:!0,autoOffset:function(a,c){this.setDelta(a-this.startPageX,c-this.startPageY)},setDelta:function(a,c){this.deltaX=a;this.deltaY=c},setDragElPos:function(a,c){this.alignElWithMouse(this.getDragEl(),a,c)},alignElWithMouse:function(a,c,b){var d=this.getTargetCoord(c,b);if(this.deltaSetXY){YAHOO.util.Dom.setStyle(a,"left",d.x+this.deltaSetXY[0]+"px");YAHOO.util.Dom.setStyle(a,"top",d.y+this.deltaSetXY[1]+"px")}else{YAHOO.util.Dom.setXY(a,[d.x,d.y]); +c=parseInt(YAHOO.util.Dom.getStyle(a,"left"),10);b=parseInt(YAHOO.util.Dom.getStyle(a,"top"),10);this.deltaSetXY=[c-d.x,b-d.y]}this.cachePosition(d.x,d.y);var f=this;setTimeout(function(){f.autoScroll.call(f,d.x,d.y,a.offsetHeight,a.offsetWidth)},0)},cachePosition:function(a,c){if(a){this.lastPageX=a;this.lastPageY=c}else{var b=YAHOO.util.Dom.getXY(this.getEl());this.lastPageX=b[0];this.lastPageY=b[1]}},autoScroll:function(a,c,b,d){if(this.scroll){var f=this.DDM.getClientHeight(),g=this.DDM.getClientWidth(), +j=this.DDM.getScrollTop(),h=this.DDM.getScrollLeft(),d=d+a,e=f+j-c-this.deltaY,i=g+h-a-this.deltaX,k=document.all?80:30;b+c>f&&e<40&&window.scrollTo(h,j+k);c0&&c-j<40)&&window.scrollTo(h,j-k);d>g&&i<40&&window.scrollTo(h+k,j);a0&&a-h<40)&&window.scrollTo(h-k,j)}},applyConfig:function(){YAHOO.util.DD.superclass.applyConfig.call(this);this.scroll=this.config.scroll!==false},b4MouseDown:function(a){this.setStartPosition();this.autoOffset(YAHOO.util.Event.getPageX(a),YAHOO.util.Event.getPageY(a))}, +b4Drag:function(a){this.setDragElPos(YAHOO.util.Event.getPageX(a),YAHOO.util.Event.getPageY(a))},toString:function(){return"DD "+this.id}});YAHOO.util.DDProxy=function(a,c,b){if(a){this.init(a,c,b);this.initFrame()}};YAHOO.util.DDProxy.dragElId="ygddfdiv"; +YAHOO.extend(YAHOO.util.DDProxy,YAHOO.util.DD,{resizeFrame:!0,centerFrame:!1,createFrame:function(){var a=this,c=document.body;if(!c||!c.firstChild)setTimeout(function(){a.createFrame()},50);else{var b=this.getDragEl(),d=YAHOO.util.Dom;if(!b){b=document.createElement("div");b.id=this.dragElId;var f=b.style;f.position="absolute";f.visibility="hidden";f.cursor="move";f.border="2px solid #aaa";f.zIndex=999;f.height="25px";f.width="25px";f=document.createElement("div");d.setStyle(f,"height","100%");d.setStyle(f, +"width","100%");d.setStyle(f,"background-color","#ccc");d.setStyle(f,"opacity","0");b.appendChild(f);c.insertBefore(b,c.firstChild)}}},initFrame:function(){this.createFrame()},applyConfig:function(){YAHOO.util.DDProxy.superclass.applyConfig.call(this);this.resizeFrame=this.config.resizeFrame!==false;this.centerFrame=this.config.centerFrame;this.setDragElId(this.config.dragElId||YAHOO.util.DDProxy.dragElId)},showFrame:function(a,c){this.getEl();var b=this.getDragEl(),d=b.style;this._resizeProxy(); +this.centerFrame&&this.setDelta(Math.round(parseInt(d.width,10)/2),Math.round(parseInt(d.height,10)/2));this.setDragElPos(a,c);YAHOO.util.Dom.setStyle(b,"visibility","visible")},_resizeProxy:function(){if(this.resizeFrame){var a=YAHOO.util.Dom,c=this.getEl(),b=this.getDragEl(),d=parseInt(a.getStyle(b,"borderTopWidth"),10),f=parseInt(a.getStyle(b,"borderRightWidth"),10),g=parseInt(a.getStyle(b,"borderBottomWidth"),10),j=parseInt(a.getStyle(b,"borderLeftWidth"),10);isNaN(d)&&(d=0);isNaN(f)&&(f=0);isNaN(g)&& +(g=0);isNaN(j)&&(j=0);f=Math.max(0,c.offsetWidth-f-j);c=Math.max(0,c.offsetHeight-d-g);a.setStyle(b,"width",f+"px");a.setStyle(b,"height",c+"px")}},b4MouseDown:function(a){this.setStartPosition();var c=YAHOO.util.Event.getPageX(a),a=YAHOO.util.Event.getPageY(a);this.autoOffset(c,a)},b4StartDrag:function(a,c){this.showFrame(a,c)},b4EndDrag:function(){YAHOO.util.Dom.setStyle(this.getDragEl(),"visibility","hidden")},endDrag:function(){var a=YAHOO.util.Dom,c=this.getEl(),b=this.getDragEl();a.setStyle(b, +"visibility","");a.setStyle(c,"visibility","hidden");YAHOO.util.DDM.moveToEl(c,b);a.setStyle(b,"visibility","hidden");a.setStyle(c,"visibility","")},toString:function(){return"DDProxy "+this.id}});YAHOO.util.DDTarget=function(a,c,b){a&&this.initTarget(a,c,b)};YAHOO.extend(YAHOO.util.DDTarget,YAHOO.util.DragDrop,{toString:function(){return"DDTarget "+this.id}});YAHOO.register("dragdrop",YAHOO.util.DragDropMgr,{version:"2.9.0",build:"2800"}); +YAHOO.util.Attribute=function(a,c){if(c){this.owner=c;this.configure(a,true)}};YAHOO.util.Attribute.INVALID_VALUE={}; +YAHOO.util.Attribute.prototype={name:void 0,value:null,owner:null,readOnly:!1,writeOnce:!1,_initialConfig:null,_written:!1,method:null,setter:null,getter:null,validator:null,getValue:function(){var a=this.value;this.getter&&(a=this.getter.call(this.owner,this.name,a));return a},setValue:function(a,c){var b,d=this.owner,f=this.name,g=YAHOO.util.Attribute.INVALID_VALUE,j={type:f,prevValue:this.getValue(),newValue:a};if(this.readOnly||this.writeOnce&&this._written||this.validator&&!this.validator.call(d, +a))return false;if(!c){b=d.fireBeforeChangeEvent(j);if(b===false)return false}if(this.setter){a=this.setter.call(d,a,this.name);if(a===g)return false}if(this.method&&this.method.call(d,a,this.name)===g)return false;this.value=a;this._written=true;j.type=f;c||this.owner.fireChangeEvent(j);return true},configure:function(a,c){a=a||{};if(c)this._written=false;this._initialConfig=this._initialConfig||{};for(var b in a)if(a.hasOwnProperty(b)){this[b]=a[b];c&&(this._initialConfig[b]=a[b])}},resetValue:function(){return this.setValue(this._initialConfig.value)}, +resetConfig:function(){this.configure(this._initialConfig,true)},refresh:function(a){this.setValue(this.value,a)}}; +(function(){var a=YAHOO.util.Lang;YAHOO.util.AttributeProvider=function(){};YAHOO.util.AttributeProvider.prototype={_configs:null,get:function(a){this._configs=this._configs||{};var b=this._configs[a];return!b||!this._configs.hasOwnProperty(a)?null:b.getValue()},set:function(a,b,d){this._configs=this._configs||{};a=this._configs[a];return!a?false:a.setValue(b,d)},getAttributeKeys:function(){this._configs=this._configs;var c=[],b;for(b in this._configs)a.hasOwnProperty(this._configs,b)&&!a.isUndefined(this._configs[b])&& +(c[c.length]=b);return c},setAttributes:function(c,b){for(var d in c)a.hasOwnProperty(c,d)&&this.set(d,c[d],b)},resetValue:function(a,b){this._configs=this._configs||{};if(this._configs[a]){this.set(a,this._configs[a]._initialConfig.value,b);return true}return false},refresh:function(c,b){for(var d=this._configs=this._configs||{},c=(a.isString(c)?[c]:c)||this.getAttributeKeys(),f=0,g=c.length;f0)if(j){var h=j.length;if(h>0){var e=null;this.fireEvent("cacheRequestEvent",{request:a,callback:b,caller:c});for(var i=h-1;i>=0;i--){var k=j[i];if(this.isCacheHit(a,k.request)){e=k.response;this.fireEvent("cacheResponseEvent",{request:a,response:e,callback:b,caller:c});if(i=this.maxCacheEntries;)g.shift();c=this.cloneBeforeCaching?b._cloneObject(c):c;g[g.length]={request:a,response:c};this.fireEvent("responseCacheEvent",{request:a,response:c})}},flushCache:function(){if(this._aCache){this._aCache=[];this.fireEvent("cacheFlushEvent")}},setInterval:function(b,c,g,j){if(a.isNumber(b)&&b>=0){var h=this,b=setInterval(function(){h.makeConnection(c, +g,j)},b);this._aIntervals.push(b);return b}},clearInterval:function(a){for(var b=this._aIntervals||[],c=b.length-1;c>-1;c--)if(b[c]===a){b.splice(c,1);clearInterval(a)}},clearAllIntervals:function(){for(var a=this._aIntervals||[],b=a.length-1;b>-1;b--)clearInterval(a[b])},sendRequest:function(a,c,g){var j=this.getCachedResponse(a,c,g);if(j){b.issueCallback(c,[a,j],false,g);return null}return this.makeConnection(a,c,g)},makeConnection:function(a,c,g){var j=b._nTransactionId++;this.fireEvent("requestEvent", +{tId:j,request:a,callback:c,caller:g});this.handleResponse(a,this.liveData,c,g,j);return j},handleResponse:function(d,c,g,j,h){this.fireEvent("responseEvent",{tId:h,request:d,response:c,callback:g,caller:j});var e=this.dataType==b.TYPE_XHR?true:false,i=null,k=c;if(this.responseType===b.TYPE_UNKNOWN)if(i=c&&c.getResponseHeader?c.getResponseHeader["Content-Type"]:null)if(i.indexOf("text/xml")>-1)this.responseType=b.TYPE_XML;else if(i.indexOf("application/json")>-1)this.responseType=b.TYPE_JSON;else{if(i.indexOf("text/plain")> +-1)this.responseType=b.TYPE_TEXT}else if(YAHOO.lang.isArray(c))this.responseType=b.TYPE_JSARRAY;else if(c&&c.nodeType&&(c.nodeType===9||c.nodeType===1||c.nodeType===11))this.responseType=b.TYPE_XML;else if(c&&c.nodeName&&c.nodeName.toLowerCase()=="table")this.responseType=b.TYPE_HTMLTABLE;else if(YAHOO.lang.isObject(c))this.responseType=b.TYPE_JSON;else if(YAHOO.lang.isString(c))this.responseType=b.TYPE_TEXT;switch(this.responseType){case b.TYPE_JSARRAY:if(e&&c&&c.responseText)k=c.responseText;try{if(a.isString(k)){var l= +[k].concat(this.parseJSONArgs);if(a.JSON)k=a.JSON.parse.apply(a.JSON,l);else if(window.JSON&&JSON.parse)k=JSON.parse.apply(JSON,l);else if(k.parseJSON)k=k.parseJSON.apply(k,l.slice(1));else{for(;k.length>0&&k.charAt(0)!="{"&&k.charAt(0)!="[";)k=k.substring(1,k.length);if(k.length>0)var q=Math.max(k.lastIndexOf("]"),k.lastIndexOf("}")),k=k.substring(0,q+1),k=eval("("+k+")")}}}catch(p){}k=this.doBeforeParseData(d,k,g);i=this.parseArrayData(d,k);break;case b.TYPE_JSON:if(e&&c&&c.responseText)k=c.responseText; +try{if(a.isString(k)){l=[k].concat(this.parseJSONArgs);if(a.JSON)k=a.JSON.parse.apply(a.JSON,l);else if(window.JSON&&JSON.parse)k=JSON.parse.apply(JSON,l);else if(k.parseJSON)k=k.parseJSON.apply(k,l.slice(1));else{for(;k.length>0&&k.charAt(0)!="{"&&k.charAt(0)!="[";)k=k.substring(1,k.length);if(k.length>0)var n=Math.max(k.lastIndexOf("]"),k.lastIndexOf("}")),k=k.substring(0,n+1),k=eval("("+k+")")}}}catch(o){}k=this.doBeforeParseData(d,k,g);i=this.parseJSONData(d,k);break;case b.TYPE_HTMLTABLE:if(e&& +c.responseText){e=document.createElement("div");e.innerHTML=c.responseText;k=e.getElementsByTagName("table")[0]}k=this.doBeforeParseData(d,k,g);i=this.parseHTMLTableData(d,k);break;case b.TYPE_XML:if(e&&c.responseXML)k=c.responseXML;k=this.doBeforeParseData(d,k,g);i=this.parseXMLData(d,k);break;case b.TYPE_TEXT:if(e&&a.isString(c.responseText))k=c.responseText;k=this.doBeforeParseData(d,k,g);i=this.parseTextData(d,k);break;default:k=this.doBeforeParseData(d,k,g);i=this.parseData(d,k)}i=i||{};if(!i.results)i.results= +[];if(!i.meta)i.meta={};if(i.error){i.error=true;this.fireEvent("dataErrorEvent",{request:d,response:c,callback:g,caller:j,message:b.ERROR_DATANULL})}else{i=this.doBeforeCallback(d,k,i,g);this.fireEvent("responseParseEvent",{request:d,response:i,callback:g,caller:j});this.addToCache(d,i)}i.tId=h;b.issueCallback(g,[d,i],i.error,j)},doBeforeParseData:function(a,b){return b},doBeforeCallback:function(a,b,c){return c},parseData:function(b,c){return a.isValue(c)?{results:c,meta:{}}:null},parseArrayData:function(d, +c){if(a.isArray(c)){var g=[],j,h,e,i,k;if(a.isArray(this.responseSchema.fields)){var l=this.responseSchema.fields;for(j=l.length-1;j>=0;--j)typeof l[j]!=="object"&&(l[j]={key:l[j]});var q={};for(j=l.length-1;j>=0;--j)(h=(typeof l[j].parser==="function"?l[j].parser:b.Parser[l[j].parser+""])||l[j].converter)&&(q[l[j].key]=h);var p=a.isArray(c[0]);for(j=c.length-1;j>-1;j--){var n={};e=c[j];if(typeof e==="object")for(h=l.length-1;h>-1;h--){i=l[h];k=p?e[h]:e[i.key];q[i.key]&&(k=q[i.key].call(this,k)); +k===void 0&&(k=null);n[i.key]=k}else if(a.isString(e))for(h=l.length-1;h>-1;h--){i=l[h];k=e;q[i.key]&&(k=q[i.key].call(this,k));k===void 0&&(k=null);n[i.key]=k}g[j]=n}}else g=c;return{results:g}}return null},parseTextData:function(d,c){if(a.isString(c)&&a.isString(this.responseSchema.recordDelim)&&a.isString(this.responseSchema.fieldDelim)){var g={results:[]},j=this.responseSchema.recordDelim,h=this.responseSchema.fieldDelim;if(c.length>0){var e=c.length-j.length;c.substr(e)==j&&(c=c.substr(0,e)); +if(c.length>0)for(var j=c.split(j),e=0,i=j.length,k=0;e0){var q=j[e].split(h),p={};if(a.isArray(this.responseSchema.fields))for(var n=this.responseSchema.fields,o=n.length-1;o>-1;o--)try{var m=q[o];if(a.isString(m)){m.charAt(0)=='"'&&(m=m.substr(1));m.charAt(m.length-1)=='"'&&(m=m.substr(0,m.length-1));var r=n[o],s=a.isValue(r.key)?r.key:r;if(!r.parser&&r.converter)r.parser=r.converter;var t=typeof r.parser==="function"?r.parser:b.Parser[r.parser+ +""];t&&(m=t.call(this,m));m===void 0&&(m=null);p[s]=m}else l=true}catch(u){l=true}else p=q;l||(g.results[k++]=p)}}}return g}return null},parseXMLResult:function(d){var c={},g=this.responseSchema;try{for(var j=g.fields.length-1;j>=0;j--){var h=g.fields[j],e=a.isValue(h.key)?h.key:h,i=null;if(this.useXPath)i=YAHOO.util.DataSource._getLocationValue(h,d);else{var k=d.attributes.getNamedItem(e);if(k)i=k.value;else{var l=d.getElementsByTagName(e);if(l&&l.item(0)){var q=l.item(0),i=q?q.text?q.text:q.textContent? +q.textContent:null:null;if(!i){for(var p=[],n=0,o=q.childNodes.length;n0&&(i=p.join(""))}}}}i===null&&(i="");if(!h.parser&&h.converter)h.parser=h.converter;var m=typeof h.parser==="function"?h.parser:b.Parser[h.parser+""];m&&(i=m.call(this,i));i===void 0&&(i=null);c[e]=i}}catch(r){}return c},parseXMLData:function(b,c){var g=false,j=this.responseSchema,h={meta:{}},e=null,i=j.metaNode,k=j.metaFields||{},l,q,p;try{if(this.useXPath)for(l in k)h.meta[l]= +YAHOO.util.DataSource._getLocationValue(k[l],c);else if(i=i?c.getElementsByTagName(i)[0]:c)for(l in k)if(a.hasOwnProperty(k,l)){q=k[l];if(p=i.getElementsByTagName(q)[0])p=p.firstChild.nodeValue;else if(p=i.attributes.getNamedItem(q))p=p.value;a.isValue(p)&&(h.meta[l]=p)}e=j.resultNode?c.getElementsByTagName(j.resultNode):null}catch(n){}if(!e||!a.isArray(j.fields))g=true;else{h.results=[];for(j=e.length-1;j>=0;--j){i=this.parseXMLResult(e.item(j));h.results[j]=i}}if(g)h.error=true;return h},parseJSONData:function(d, +c){var g={results:[],meta:{}};if(a.isObject(c)&&this.responseSchema.resultsList){var j=this.responseSchema,h=j.fields,e=c,i=[],k=j.metaFields||{},l=[],q=[],p=[],n=false,o,m,r,s=function(e){var a=null,b=[],i=0;if(e){e=e.replace(/\[(['"])(.*?)\1\]/g,function(e,a,d){b[i]=d;return".@"+i++}).replace(/\[(\d+)\]/g,function(e,a){b[i]=parseInt(a,10)|0;return".@"+i++}).replace(/^\./,"");if(!/[^\w\.\$@]/.test(e)){a=e.split(".");for(i=a.length-1;i>=0;--i)a[i].charAt(0)==="@"&&(a[i]=b[parseInt(a[i].substr(1), +10)])}}return a},t=function(e,a){for(var b=a,i=0,d=e.length;i1?q[q.length]={key:o,path:r}:p[p.length]={key:o,path:r[0]})}for(j=e.length-1;j>=0;--j){n=e[j];r={};if(n){for(h= +p.length-1;h>=0;--h)r[p[h].key]=n[p[h].path]!==void 0?n[p[h].path]:n[h];for(h=q.length-1;h>=0;--h)r[q[h].key]=t(q[h].path,n);for(h=l.length-1;h>=0;--h){n=l[h].key;r[n]=l[h].parser.call(this,r[n]);r[n]===void 0&&(r[n]=null)}}i[j]=r}}else i=e;for(o in k)if(a.hasOwnProperty(k,o))if(r=s(k[o])){e=t(r,c);g.meta[o]=e}}g.results=i}else g.error=true;return g},parseHTMLTableData:function(d,c){var g=false,j=this.responseSchema.fields,h={results:[]};if(a.isArray(j))for(var e=0;e-1;k--){for(var l=i.rows[k],q={},p=j.length-1;p>-1;p--){var n=j[p],o=a.isValue(n.key)?n.key:n,m=l.cells[p].innerHTML;if(!n.parser&&n.converter)n.parser=n.converter;(n=typeof n.parser==="function"?n.parser:b.Parser[n.parser+""])&&(m=n.call(this,m));m===void 0&&(m=null);q[o]=m}h.results[k]=q}else g=true;if(g)h.error=true;return h}};a.augmentProto(b,c.EventProvider);c.LocalDataSource=function(a,f){this.dataType=b.TYPE_LOCAL;if(a)if(YAHOO.lang.isArray(a))this.responseType= +b.TYPE_JSARRAY;else if(a.nodeType&&a.nodeType==9)this.responseType=b.TYPE_XML;else if(a.nodeName&&a.nodeName.toLowerCase()=="table"){this.responseType=b.TYPE_HTMLTABLE;a=a.cloneNode(true)}else if(YAHOO.lang.isString(a))this.responseType=b.TYPE_TEXT;else{if(YAHOO.lang.isObject(a))this.responseType=b.TYPE_JSON}else{a=[];this.responseType=b.TYPE_JSARRAY}c.LocalDataSource.superclass.constructor.call(this,a,f)};a.extend(c.LocalDataSource,b);a.augmentObject(c.LocalDataSource,b);c.FunctionDataSource=function(a, +f){this.dataType=b.TYPE_JSFUNCTION;c.FunctionDataSource.superclass.constructor.call(this,a||function(){},f)};a.extend(c.FunctionDataSource,b,{scope:null,makeConnection:function(a,c,g){var j=b._nTransactionId++;this.fireEvent("requestEvent",{tId:j,request:a,callback:c,caller:g});var h=this.scope?this.liveData.call(this.scope,a,this,c):this.liveData(a,c);if(this.responseType===b.TYPE_UNKNOWN)if(YAHOO.lang.isArray(h))this.responseType=b.TYPE_JSARRAY;else if(h&&h.nodeType&&h.nodeType==9)this.responseType= +b.TYPE_XML;else if(h&&h.nodeName&&h.nodeName.toLowerCase()=="table")this.responseType=b.TYPE_HTMLTABLE;else if(YAHOO.lang.isObject(h))this.responseType=b.TYPE_JSON;else if(YAHOO.lang.isString(h))this.responseType=b.TYPE_TEXT;this.handleResponse(a,h,c,g,j);return j}});a.augmentObject(c.FunctionDataSource,b);c.ScriptNodeDataSource=function(a,f){this.dataType=b.TYPE_SCRIPTNODE;c.ScriptNodeDataSource.superclass.constructor.call(this,a||"",f)};a.extend(c.ScriptNodeDataSource,b,{getUtility:c.Get,asyncMode:"allowAll", +scriptCallbackParam:"callback",generateRequestCallback:function(a){return"&"+this.scriptCallbackParam+"=YAHOO.util.ScriptNodeDataSource.callbacks["+a+"]"},doBeforeGetScriptNode:function(a){return a},makeConnection:function(a,f,g){var j=b._nTransactionId++;this.fireEvent("requestEvent",{tId:j,request:a,callback:f,caller:g});if(c.ScriptNodeDataSource._nPending===0){c.ScriptNodeDataSource.callbacks=[];c.ScriptNodeDataSource._nId=0}var h=c.ScriptNodeDataSource._nId;c.ScriptNodeDataSource._nId++;var e= +this;c.ScriptNodeDataSource.callbacks[h]=function(i){if(e.asyncMode!=="ignoreStaleResponses"||h===c.ScriptNodeDataSource.callbacks.length-1){if(e.responseType===b.TYPE_UNKNOWN)if(YAHOO.lang.isArray(i))e.responseType=b.TYPE_JSARRAY;else if(i.nodeType&&i.nodeType==9)e.responseType=b.TYPE_XML;else if(i.nodeName&&i.nodeName.toLowerCase()=="table")e.responseType=b.TYPE_HTMLTABLE;else if(YAHOO.lang.isObject(i))e.responseType=b.TYPE_JSON;else if(YAHOO.lang.isString(i))e.responseType=b.TYPE_TEXT;e.handleResponse(a, +i,f,g,j)}delete c.ScriptNodeDataSource.callbacks[h]};c.ScriptNodeDataSource._nPending++;var i=this.liveData+a+this.generateRequestCallback(h),i=this.doBeforeGetScriptNode(i);this.getUtility.script(i,{autopurge:true,onsuccess:c.ScriptNodeDataSource._bumpPendingDown,onfail:c.ScriptNodeDataSource._bumpPendingDown});return j}});a.augmentObject(c.ScriptNodeDataSource,b);a.augmentObject(c.ScriptNodeDataSource,{_nId:0,_nPending:0,callbacks:[]});c.XHRDataSource=function(a,f){this.dataType=b.TYPE_XHR;this.connMgr= +this.connMgr||c.Connect;c.XHRDataSource.superclass.constructor.call(this,a||"",f)};a.extend(c.XHRDataSource,b,{connMgr:null,connXhrMode:"allowAll",connMethodPost:false,connTimeout:0,makeConnection:function(d,c,g){var j=b._nTransactionId++;this.fireEvent("requestEvent",{tId:j,request:d,callback:c,caller:g});var h=this.connMgr,e=this._oQueue,i={success:function(a){if(a&&this.connXhrMode=="ignoreStaleResponses"&&a.tId!=e.conn.tId)return null;if(a){if(this.responseType===b.TYPE_UNKNOWN){var i=a.getResponseHeader? +a.getResponseHeader["Content-Type"]:null;if(i)if(i.indexOf("text/xml")>-1)this.responseType=b.TYPE_XML;else if(i.indexOf("application/json")>-1)this.responseType=b.TYPE_JSON;else if(i.indexOf("text/plain")>-1)this.responseType=b.TYPE_TEXT}this.handleResponse(d,a,c,g,j)}else{this.fireEvent("dataErrorEvent",{request:d,response:null,callback:c,caller:g,message:b.ERROR_DATANULL});b.issueCallback(c,[d,{error:true}],true,g);return null}},failure:function(e){this.fireEvent("dataErrorEvent",{request:d,response:e, +callback:c,caller:g,message:b.ERROR_DATAINVALID});a.isString(this.liveData)&&a.isString(d)&&this.liveData.lastIndexOf("?")!==this.liveData.length-1&&d.indexOf("?");e=e||{};e.error=true;b.issueCallback(c,[d,e],true,g);return null},scope:this};if(a.isNumber(this.connTimeout))i.timeout=this.connTimeout;if(this.connXhrMode=="cancelStaleRequests"&&e.conn&&h.abort){h.abort(e.conn);e.conn=null}if(h&&h.asyncRequest){var k=this.liveData,l=this.connMethodPost,q=l?"POST":"GET",p=l||!a.isValue(d)?k:k+d,n=l?d: +null;if(this.connXhrMode!="queueRequests")e.conn=h.asyncRequest(q,p,i,n);else if(e.conn){var o=e.requests;o.push({request:d,callback:i});if(!e.interval)e.interval=setInterval(function(){if(!h.isCallInProgress(e.conn))if(o.length>0){p=l||!a.isValue(o[0].request)?k:k+o[0].request;n=l?o[0].request:null;e.conn=h.asyncRequest(q,p,o[0].callback,n);o.shift()}else{clearInterval(e.interval);e.interval=null}},50)}else e.conn=h.asyncRequest(q,p,i,n)}else b.issueCallback(c,[d,{error:true}],true,g);return j}}); +a.augmentObject(c.XHRDataSource,b);c.DataSource=function(a,f){var f=f||{},g=f.dataType;if(g){if(g==b.TYPE_LOCAL)return new c.LocalDataSource(a,f);if(g==b.TYPE_XHR)return new c.XHRDataSource(a,f);if(g==b.TYPE_SCRIPTNODE)return new c.ScriptNodeDataSource(a,f);if(g==b.TYPE_JSFUNCTION)return new c.FunctionDataSource(a,f)}return YAHOO.lang.isString(a)?new c.XHRDataSource(a,f):YAHOO.lang.isFunction(a)?new c.FunctionDataSource(a,f):new c.LocalDataSource(a,f)};a.augmentObject(c.DataSource,b)})(); +YAHOO.util.Number={format:function(a,c){if(a===""||a===null||!isFinite(a))return"";var a=+a,c=YAHOO.lang.merge(YAHOO.util.Number.format.defaults,c||{}),b=Math.abs(a),d=c.decimalPlaces||0,f=c.thousandsSeparator,g=c.negativeFormat||"-"+c.format,j;g.indexOf("#")>-1&&(g=g.replace(/#/,c.format));if(d<0){j=b-b%1+"";d=j.length+d;j=d>0?Number("."+j).toFixed(d).slice(2)+Array(j.length-d+1).join("0"):"0"}else if(d>0||(b+"").indexOf(".")>0){j=Math.pow(10,d);j=Math.round(b*j)/j+"";var h=j.indexOf(".");if(h<0){h= +(Math.pow(10,d)+"").substring(1);d>0&&(j=j+"."+h)}else{d=d-(j.length-h-1);h=(Math.pow(10,d)+"").substring(1);j=j+h}}else j=b.toFixed(d)+"";j=j.split(/\D/);if(b>=1E3){d=j[0].length%3||3;j[0]=j[0].slice(0,d)+j[0].slice(d).replace(/(\d{3})/g,f+"$1")}return YAHOO.util.Number.format._applyFormat(a<0?g:c.format,j.join(c.decimalSeparator),c)}};YAHOO.util.Number.format.defaults={format:"{prefix}{number}{suffix}",negativeFormat:null,decimalSeparator:".",decimalPlaces:null,thousandsSeparator:""}; +YAHOO.util.Number.format._applyFormat=function(a,c,b){return a.replace(/\{(\w+)\}/g,function(a,f){return f==="number"?c:f in b?b[f]:""})}; +(function(){var a=function(a,d,c){for(typeof c==="undefined"&&(c=10);parseInt(a,10)1;c=c/10)a=d.toString()+a;return a.toString()},c={formats:{a:function(a,d){return d.a[a.getDay()]},A:function(a,d){return d.A[a.getDay()]},b:function(a,d){return d.b[a.getMonth()]},B:function(a,d){return d.B[a.getMonth()]},C:function(b){return a(parseInt(b.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(b){return a(parseInt(c.formats.G(b)%100,10),0)},G:function(a){var d=a.getFullYear(), +f=parseInt(c.formats.V(a),10),a=parseInt(c.formats.W(a),10);a>f?d++:a===0&&f>=52&&d--;return d},H:["getHours","0"],I:function(b){b=b.getHours()%12;return a(b===0?12:b,0)},j:function(b){var d=new Date(""+b.getFullYear()+"/1/1 GMT"),b=new Date(""+b.getFullYear()+"/"+(b.getMonth()+1)+"/"+b.getDate()+" GMT")-d,b=parseInt(b/6E4/60/24,10)+1;return a(b,0,100)},k:["getHours"," "],l:function(b){b=b.getHours()%12;return a(b===0?12:b," ")},m:function(b){return a(b.getMonth()+1,0)},M:["getMinutes","0"],p:function(a, +d){return d.p[a.getHours()>=12?1:0]},P:function(a,d){return d.P[a.getHours()>=12?1:0]},s:function(a){return parseInt(a.getTime()/1E3,10)},S:["getSeconds","0"],u:function(a){a=a.getDay();return a===0?7:a},U:function(b){var d=parseInt(c.formats.j(b),10),b=6-b.getDay(),d=parseInt((d+b)/7,10);return a(d,0)},V:function(b){var d=parseInt(c.formats.W(b),10),f=(new Date(""+b.getFullYear()+"/1/1")).getDay(),d=d+(f>4||f<=1?0:1);d===53&&(new Date(""+b.getFullYear()+"/12/31")).getDay()<4?d=1:d===0&&(d=c.formats.V(new Date(""+ +(b.getFullYear()-1)+"/12/31")));return a(d,0)},w:"getDay",W:function(b){var d=parseInt(c.formats.j(b),10),b=7-c.formats.u(b),d=parseInt((d+b)/7,10);return a(d,0,10)},y:function(b){return a(b.getFullYear()%100,0)},Y:"getFullYear",z:function(b){var b=b.getTimezoneOffset(),d=a(parseInt(Math.abs(b/60),10),0),c=a(Math.abs(b%60),0);return(b>0?"-":"+")+d+c},Z:function(a){var d=a.toString().replace(/^.*:\d\d( GMT[+-]\d+)? \(?([A-Za-z ]+)\)?\d*$/,"$2").replace(/[a-z ]/g,"");d.length>4&&(d=c.formats.z(a)); +return d},"%":function(){return"%"}},aggregates:{c:"locale",D:"%m/%d/%y",F:"%Y-%m-%d",h:"%b",n:"\n",r:"locale",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"},format:function(b,d,f){d=d||{};if(!(b instanceof Date))return YAHOO.lang.isValue(b)?b:"";d=d.format||"%m/%d/%Y";d==="YYYY/MM/DD"?d="%Y/%m/%d":d==="DD/MM/YYYY"?d="%d/%m/%Y":d==="MM/DD/YYYY"&&(d="%m/%d/%Y");f=f||"en";f in YAHOO.util.DateLocale||(f=f.replace(/-[a-zA-Z]+$/,"")in YAHOO.util.DateLocale?f.replace(/-[a-zA-Z]+$/,""):"en");for(var g= +YAHOO.util.DateLocale[f],f=function(a,e){var b=c.aggregates[e];return b==="locale"?g[e]:b},j=function(d,e){var i=c.formats[e];return typeof i==="string"?b[i]():typeof i==="function"?i.call(b,b,g):typeof i==="object"&&typeof i[0]==="string"?a(b[i[0]](),i[1]):e};d.match(/%[cDFhnrRtTxX]/);)d=d.replace(/%([cDFhnrRtTxX])/g,f);d=d.replace(/%([aAbBCdegGHIjklmMpPsSuUVwWyYzZ%])/g,j);f=j=void 0;return d}};YAHOO.namespace("YAHOO.util");YAHOO.util.Date=c;YAHOO.util.DateLocale={a:["Sun","Mon","Tue","Wed","Thu", +"Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],r:"%I:%M:%S %p",x:"%d/%m/%y",X:"%T"};YAHOO.util.DateLocale.en=YAHOO.lang.merge(YAHOO.util.DateLocale,{});YAHOO.util.DateLocale["en-US"]=YAHOO.lang.merge(YAHOO.util.DateLocale.en, +{c:"%a %d %b %Y %I:%M:%S %p %Z",x:"%m/%d/%Y",X:"%I:%M:%S %p"});YAHOO.util.DateLocale["en-GB"]=YAHOO.lang.merge(YAHOO.util.DateLocale.en,{r:"%l:%M:%S %P %Z"});YAHOO.util.DateLocale["en-AU"]=YAHOO.lang.merge(YAHOO.util.DateLocale.en)})();YAHOO.register("datasource",YAHOO.util.DataSource,{version:"2.9.0",build:"2800"});YAHOO.widget.DS_JSArray=YAHOO.util.LocalDataSource;YAHOO.widget.DS_JSFunction=YAHOO.util.FunctionDataSource; +YAHOO.widget.DS_XHR=function(a,c,b){a=new YAHOO.util.XHRDataSource(a,b);a._aDeprecatedSchema=c;return a};YAHOO.widget.DS_ScriptNode=function(a,c,b){a=new YAHOO.util.ScriptNodeDataSource(a,b);a._aDeprecatedSchema=c;return a};YAHOO.widget.DS_XHR.TYPE_JSON=YAHOO.util.DataSourceBase.TYPE_JSON;YAHOO.widget.DS_XHR.TYPE_XML=YAHOO.util.DataSourceBase.TYPE_XML;YAHOO.widget.DS_XHR.TYPE_FLAT=YAHOO.util.DataSourceBase.TYPE_TEXT; +YAHOO.widget.AutoComplete=function(a,c,b,d){if(a&&c&&b&&b&&YAHOO.lang.isFunction(b.sendRequest)){this.dataSource=b;this.key=0;var f=b.responseSchema;if(b._aDeprecatedSchema){var g=b._aDeprecatedSchema;if(YAHOO.lang.isArray(g)){if(b.responseType===YAHOO.util.DataSourceBase.TYPE_JSON||b.responseType===YAHOO.util.DataSourceBase.TYPE_UNKNOWN){f.resultsList=g[0];this.key=g[1];f.fields=g.length<3?null:g.slice(1)}else if(b.responseType===YAHOO.util.DataSourceBase.TYPE_XML){f.resultNode=g[0];this.key=g[1]; +f.fields=g.slice(1)}else if(b.responseType===YAHOO.util.DataSourceBase.TYPE_TEXT){f.recordDelim=g[0];f.fieldDelim=g[1]}b.responseSchema=f}}if(YAHOO.util.Dom.inDocument(a)){if(YAHOO.lang.isString(a)){this._sName="instance"+YAHOO.widget.AutoComplete._nIndex+" "+a;this._elTextbox=document.getElementById(a)}else{this._sName=a.id?"instance"+YAHOO.widget.AutoComplete._nIndex+" "+a.id:"instance"+YAHOO.widget.AutoComplete._nIndex;this._elTextbox=a}YAHOO.util.Dom.addClass(this._elTextbox,"yui-ac-input");if(YAHOO.util.Dom.inDocument(c)){this._elContainer= +YAHOO.lang.isString(c)?document.getElementById(c):c;a=this._elContainer.parentNode;a.tagName.toLowerCase()=="div"&&YAHOO.util.Dom.addClass(a,"yui-ac");if(this.dataSource.dataType===YAHOO.util.DataSourceBase.TYPE_LOCAL)this.applyLocalFilter=true;if(d&&d.constructor==Object)for(var j in d)j&&(this[j]=d[j]);this._initContainerEl();this._initProps();this._initListEl();this._initContainerHelperEls();d=this._elTextbox;YAHOO.util.Event.addListener(d,"keyup",this._onTextboxKeyUp,this);YAHOO.util.Event.addListener(d, +"keydown",this._onTextboxKeyDown,this);YAHOO.util.Event.addListener(d,"focus",this._onTextboxFocus,this);YAHOO.util.Event.addListener(d,"blur",this._onTextboxBlur,this);YAHOO.util.Event.addListener(c,"mouseover",this._onContainerMouseover,this);YAHOO.util.Event.addListener(c,"mouseout",this._onContainerMouseout,this);YAHOO.util.Event.addListener(c,"click",this._onContainerClick,this);YAHOO.util.Event.addListener(c,"scroll",this._onContainerScroll,this);YAHOO.util.Event.addListener(c,"resize",this._onContainerResize, +this);YAHOO.util.Event.addListener(d,"keypress",this._onTextboxKeyPress,this);YAHOO.util.Event.addListener(window,"unload",this._onWindowUnload,this);this.textboxFocusEvent=new YAHOO.util.CustomEvent("textboxFocus",this);this.textboxKeyEvent=new YAHOO.util.CustomEvent("textboxKey",this);this.textboxKeyUpEvent=new YAHOO.util.CustomEvent("textboxKeyUp",this);this.dataRequestEvent=new YAHOO.util.CustomEvent("dataRequest",this);this.dataRequestCancelEvent=new YAHOO.util.CustomEvent("dataRequestCancel", +this);this.dataReturnEvent=new YAHOO.util.CustomEvent("dataReturn",this);this.dataErrorEvent=new YAHOO.util.CustomEvent("dataError",this);this.containerPopulateEvent=new YAHOO.util.CustomEvent("containerPopulate",this);this.containerExpandEvent=new YAHOO.util.CustomEvent("containerExpand",this);this.typeAheadEvent=new YAHOO.util.CustomEvent("typeAhead",this);this.itemMouseOverEvent=new YAHOO.util.CustomEvent("itemMouseOver",this);this.itemMouseOutEvent=new YAHOO.util.CustomEvent("itemMouseOut",this); +this.itemArrowToEvent=new YAHOO.util.CustomEvent("itemArrowTo",this);this.itemArrowFromEvent=new YAHOO.util.CustomEvent("itemArrowFrom",this);this.itemSelectEvent=new YAHOO.util.CustomEvent("itemSelect",this);this.unmatchedItemSelectEvent=new YAHOO.util.CustomEvent("unmatchedItemSelect",this);this.selectionEnforceEvent=new YAHOO.util.CustomEvent("selectionEnforce",this);this.containerCollapseEvent=new YAHOO.util.CustomEvent("containerCollapse",this);this.textboxBlurEvent=new YAHOO.util.CustomEvent("textboxBlur", +this);this.textboxChangeEvent=new YAHOO.util.CustomEvent("textboxChange",this);d.setAttribute("autocomplete","off");YAHOO.widget.AutoComplete._nIndex++}}}};YAHOO.widget.AutoComplete.prototype.dataSource=null;YAHOO.widget.AutoComplete.prototype.applyLocalFilter=null;YAHOO.widget.AutoComplete.prototype.queryMatchCase=!1;YAHOO.widget.AutoComplete.prototype.queryMatchContains=!1;YAHOO.widget.AutoComplete.prototype.queryMatchSubset=!1;YAHOO.widget.AutoComplete.prototype.minQueryLength=1; +YAHOO.widget.AutoComplete.prototype.maxResultsDisplayed=10;YAHOO.widget.AutoComplete.prototype.queryDelay=0.2;YAHOO.widget.AutoComplete.prototype.typeAheadDelay=0.5;YAHOO.widget.AutoComplete.prototype.queryInterval=500;YAHOO.widget.AutoComplete.prototype.highlightClassName="yui-ac-highlight";YAHOO.widget.AutoComplete.prototype.prehighlightClassName=null;YAHOO.widget.AutoComplete.prototype.delimChar=null;YAHOO.widget.AutoComplete.prototype.autoHighlight=!0; +YAHOO.widget.AutoComplete.prototype.typeAhead=!1;YAHOO.widget.AutoComplete.prototype.animHoriz=!1;YAHOO.widget.AutoComplete.prototype.animVert=!0;YAHOO.widget.AutoComplete.prototype.animSpeed=0.3;YAHOO.widget.AutoComplete.prototype.forceSelection=!1;YAHOO.widget.AutoComplete.prototype.allowBrowserAutocomplete=!0;YAHOO.widget.AutoComplete.prototype.alwaysShowContainer=!1;YAHOO.widget.AutoComplete.prototype.useIFrame=!1;YAHOO.widget.AutoComplete.prototype.useShadow=!1; +YAHOO.widget.AutoComplete.prototype.suppressInputUpdate=!1;YAHOO.widget.AutoComplete.prototype.resultTypeList=!0;YAHOO.widget.AutoComplete.prototype.queryQuestionMark=!0;YAHOO.widget.AutoComplete.prototype.autoSnapContainer=!0;YAHOO.widget.AutoComplete.prototype.toString=function(){return"AutoComplete "+this._sName};YAHOO.widget.AutoComplete.prototype.getInputEl=function(){return this._elTextbox};YAHOO.widget.AutoComplete.prototype.getContainerEl=function(){return this._elContainer}; +YAHOO.widget.AutoComplete.prototype.isFocused=function(){return this._bFocused};YAHOO.widget.AutoComplete.prototype.isContainerOpen=function(){return this._bContainerOpen};YAHOO.widget.AutoComplete.prototype.getListEl=function(){return this._elList};YAHOO.widget.AutoComplete.prototype.getListItemMatch=function(a){return a._sResultMatch?a._sResultMatch:null};YAHOO.widget.AutoComplete.prototype.getListItemData=function(a){return a._oResultData?a._oResultData:null}; +YAHOO.widget.AutoComplete.prototype.getListItemIndex=function(a){return YAHOO.lang.isNumber(a._nItemIndex)?a._nItemIndex:null};YAHOO.widget.AutoComplete.prototype.setHeader=function(a){if(this._elHeader){var c=this._elHeader;if(a){c.innerHTML=a;c.style.display=""}else{c.innerHTML="";c.style.display="none"}}};YAHOO.widget.AutoComplete.prototype.setFooter=function(a){if(this._elFooter){var c=this._elFooter;if(a){c.innerHTML=a;c.style.display=""}else{c.innerHTML="";c.style.display="none"}}}; +YAHOO.widget.AutoComplete.prototype.setBody=function(a){if(this._elBody){var c=this._elBody;YAHOO.util.Event.purgeElement(c,true);if(a){c.innerHTML=a;c.style.display=""}else{c.innerHTML="";c.style.display="none"}this._elList=null}}; +YAHOO.widget.AutoComplete.prototype.generateRequest=function(a){var c=this.dataSource.dataType;c===YAHOO.util.DataSourceBase.TYPE_XHR?a=this.dataSource.connMethodPost?(this.dataSource.scriptQueryParam||"query")+"="+a+(this.dataSource.scriptQueryAppend?"&"+this.dataSource.scriptQueryAppend:""):(this.queryQuestionMark?"?":"")+(this.dataSource.scriptQueryParam||"query")+"="+a+(this.dataSource.scriptQueryAppend?"&"+this.dataSource.scriptQueryAppend:""):c===YAHOO.util.DataSourceBase.TYPE_SCRIPTNODE&&(a= +"&"+(this.dataSource.scriptQueryParam||"query")+"="+a+(this.dataSource.scriptQueryAppend?"&"+this.dataSource.scriptQueryAppend:""));return a};YAHOO.widget.AutoComplete.prototype.sendQuery=function(a){this._bFocused=true;this._sendQuery(this.delimChar?this._elTextbox.value+a:a)};YAHOO.widget.AutoComplete.prototype.snapContainer=function(){var a=this._elTextbox,c=YAHOO.util.Dom.getXY(a);c[1]=c[1]+(YAHOO.util.Dom.get(a).offsetHeight+2);YAHOO.util.Dom.setXY(this._elContainer,c)}; +YAHOO.widget.AutoComplete.prototype.expandContainer=function(){this._toggleContainer(true)};YAHOO.widget.AutoComplete.prototype.collapseContainer=function(){this._toggleContainer(false)};YAHOO.widget.AutoComplete.prototype.clearList=function(){for(var a=this._elList.childNodes,c=a.length-1;c>-1;c--)a[c].style.display="none"}; +YAHOO.widget.AutoComplete.prototype.getSubsetMatches=function(a){for(var c,b=a.length;b>=this.minQueryLength;b--){c=this.generateRequest(a.substr(0,b));this.dataRequestEvent.fire(this,void 0,c);if(c=this.dataSource.getCachedResponse(c))return this.filterResults.apply(this.dataSource,[a,c,c,{scope:this}])}return null}; +YAHOO.widget.AutoComplete.prototype.preparseRawResponse=function(a,c){var b=this.responseStripAfter!==""&&c.indexOf?c.indexOf(this.responseStripAfter):-1;b!=-1&&(c=c.substring(0,b));return c}; +YAHOO.widget.AutoComplete.prototype.filterResults=function(a,c,b,d){if(d&&d.argument&&YAHOO.lang.isValue(d.argument.query))a=d.argument.query;if(a&&a!==""){for(var b=YAHOO.widget.AutoComplete._cloneObject(b),f=d.scope,c=b.results,d=[],g=f.maxResultsDisplayed,j=this.queryMatchCase||f.queryMatchCase,f=this.queryMatchContains||f.queryMatchContains,h=0,e=c.length;h-1)&&d.push(i)}if(e>g&&d.length===g)break}b.results=d}return b};YAHOO.widget.AutoComplete.prototype.handleResponse=function(a,c,b){this instanceof YAHOO.widget.AutoComplete&&this._sName&&this._populateList(a,c,b)};YAHOO.widget.AutoComplete.prototype.doBeforeLoadData=function(){return true}; +YAHOO.widget.AutoComplete.prototype.formatResult=function(a,c,b){return b?b:""};YAHOO.widget.AutoComplete.prototype.formatEscapedResult=function(a,c,b){return YAHOO.lang.escapeHTML(b?b:"")};YAHOO.widget.AutoComplete.prototype.doBeforeExpandContainer=function(){return true}; +YAHOO.widget.AutoComplete.prototype.destroy=function(){var a=this._elTextbox,c=this._elContainer;this.textboxFocusEvent.unsubscribeAll();this.textboxKeyEvent.unsubscribeAll();this.textboxKeyUpEvent.unsubscribeAll();this.dataRequestEvent.unsubscribeAll();this.dataReturnEvent.unsubscribeAll();this.dataErrorEvent.unsubscribeAll();this.containerPopulateEvent.unsubscribeAll();this.containerExpandEvent.unsubscribeAll();this.typeAheadEvent.unsubscribeAll();this.itemMouseOverEvent.unsubscribeAll();this.itemMouseOutEvent.unsubscribeAll(); +this.itemArrowToEvent.unsubscribeAll();this.itemArrowFromEvent.unsubscribeAll();this.itemSelectEvent.unsubscribeAll();this.unmatchedItemSelectEvent.unsubscribeAll();this.selectionEnforceEvent.unsubscribeAll();this.containerCollapseEvent.unsubscribeAll();this.textboxBlurEvent.unsubscribeAll();this.textboxChangeEvent.unsubscribeAll();YAHOO.util.Event.purgeElement(a,true);YAHOO.util.Event.purgeElement(c,true);c.innerHTML="";for(var b in this)YAHOO.lang.hasOwnProperty(this,b)&&(this[b]=null)}; +YAHOO.widget.AutoComplete.prototype.textboxFocusEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyEvent=null;YAHOO.widget.AutoComplete.prototype.textboxKeyUpEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestEvent=null;YAHOO.widget.AutoComplete.prototype.dataRequestCancelEvent=null;YAHOO.widget.AutoComplete.prototype.dataReturnEvent=null;YAHOO.widget.AutoComplete.prototype.dataErrorEvent=null;YAHOO.widget.AutoComplete.prototype.containerPopulateEvent=null; +YAHOO.widget.AutoComplete.prototype.containerExpandEvent=null;YAHOO.widget.AutoComplete.prototype.typeAheadEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOverEvent=null;YAHOO.widget.AutoComplete.prototype.itemMouseOutEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowToEvent=null;YAHOO.widget.AutoComplete.prototype.itemArrowFromEvent=null;YAHOO.widget.AutoComplete.prototype.itemSelectEvent=null;YAHOO.widget.AutoComplete.prototype.unmatchedItemSelectEvent=null; +YAHOO.widget.AutoComplete.prototype.selectionEnforceEvent=null;YAHOO.widget.AutoComplete.prototype.containerCollapseEvent=null;YAHOO.widget.AutoComplete.prototype.textboxBlurEvent=null;YAHOO.widget.AutoComplete.prototype.textboxChangeEvent=null;YAHOO.widget.AutoComplete._nIndex=0;YAHOO.widget.AutoComplete.prototype._sName=null;YAHOO.widget.AutoComplete.prototype._elTextbox=null;YAHOO.widget.AutoComplete.prototype._elContainer=null;YAHOO.widget.AutoComplete.prototype._elContent=null; +YAHOO.widget.AutoComplete.prototype._elHeader=null;YAHOO.widget.AutoComplete.prototype._elBody=null;YAHOO.widget.AutoComplete.prototype._elFooter=null;YAHOO.widget.AutoComplete.prototype._elShadow=null;YAHOO.widget.AutoComplete.prototype._elIFrame=null;YAHOO.widget.AutoComplete.prototype._bFocused=!1;YAHOO.widget.AutoComplete.prototype._oAnim=null;YAHOO.widget.AutoComplete.prototype._bContainerOpen=!1;YAHOO.widget.AutoComplete.prototype._bOverContainer=!1; +YAHOO.widget.AutoComplete.prototype._elList=null;YAHOO.widget.AutoComplete.prototype._nDisplayedItems=0;YAHOO.widget.AutoComplete.prototype._sCurQuery=null;YAHOO.widget.AutoComplete.prototype._sPastSelections="";YAHOO.widget.AutoComplete.prototype._sInitInputValue=null;YAHOO.widget.AutoComplete.prototype._elCurListItem=null;YAHOO.widget.AutoComplete.prototype._elCurPrehighlightItem=null;YAHOO.widget.AutoComplete.prototype._bItemSelected=!1;YAHOO.widget.AutoComplete.prototype._nKeyCode=null; +YAHOO.widget.AutoComplete.prototype._nDelayID=-1;YAHOO.widget.AutoComplete.prototype._nTypeAheadDelayID=-1;YAHOO.widget.AutoComplete.prototype._iFrameSrc="javascript:false;";YAHOO.widget.AutoComplete.prototype._queryInterval=null;YAHOO.widget.AutoComplete.prototype._sLastTextboxValue=null; +YAHOO.widget.AutoComplete.prototype._initProps=function(){if(!YAHOO.lang.isNumber(this.minQueryLength))this.minQueryLength=1;var a=this.maxResultsDisplayed;if(!YAHOO.lang.isNumber(a)||a<1)this.maxResultsDisplayed=10;a=this.queryDelay;if(!YAHOO.lang.isNumber(a)||a<0)this.queryDelay=0.2;a=this.typeAheadDelay;if(!YAHOO.lang.isNumber(a)||a<0)this.typeAheadDelay=0.2;a=this.delimChar;if(YAHOO.lang.isString(a)&&a.length>0)this.delimChar=[a];else if(!YAHOO.lang.isArray(a))this.delimChar=null;a=this.animSpeed; +if((this.animHoriz||this.animVert)&&YAHOO.util.Anim){if(!YAHOO.lang.isNumber(a)||a<0)this.animSpeed=0.3;this._oAnim?this._oAnim.duration=this.animSpeed:this._oAnim=new YAHOO.util.Anim(this._elContent,{},this.animSpeed)}}; +YAHOO.widget.AutoComplete.prototype._initContainerHelperEls=function(){if(this.useShadow&&!this._elShadow){var a=document.createElement("div");a.className="yui-ac-shadow";a.style.width=0;a.style.height=0;this._elShadow=this._elContainer.appendChild(a)}if(this.useIFrame&&!this._elIFrame){a=document.createElement("iframe");a.src=this._iFrameSrc;a.frameBorder=0;a.scrolling="no";a.style.position="absolute";a.style.width=0;a.style.height=0;a.style.padding=0;a.tabIndex=-1;a.role="presentation";a.title= +"Presentational iframe shim";this._elIFrame=this._elContainer.appendChild(a)}}; +YAHOO.widget.AutoComplete.prototype._initContainerEl=function(){YAHOO.util.Dom.addClass(this._elContainer,"yui-ac-container");if(!this._elContent){var a=document.createElement("div");a.className="yui-ac-content";a.style.display="none";this._elContent=this._elContainer.appendChild(a);a=document.createElement("div");a.className="yui-ac-hd";a.style.display="none";this._elHeader=this._elContent.appendChild(a);a=document.createElement("div");a.className="yui-ac-bd";this._elBody=this._elContent.appendChild(a); +a=document.createElement("div");a.className="yui-ac-ft";a.style.display="none";this._elFooter=this._elContent.appendChild(a)}}; +YAHOO.widget.AutoComplete.prototype._initListEl=function(){for(var a=this.maxResultsDisplayed,c=this._elList||document.createElement("ul"),b;c.childNodes.length=18&&a<=20||a==27||a>=33&&a<=35||a>=36&&a<=40||a>=44&&a<=45||a==229?true:false}; +YAHOO.widget.AutoComplete.prototype._sendQuery=function(a){if(this.minQueryLength<0)this._toggleContainer(false);else{if(this.delimChar){var c=this._extractQuery(a),a=c.query;this._sPastSelections=c.previous}if(a&&a.length0){this._nDelayID!=-1&&clearTimeout(this._nDelayID);this._toggleContainer(false)}else{a=encodeURIComponent(a);this._nDelayID=-1;if(this.dataSource.queryMatchSubset||this.queryMatchSubset)if(c=this.getSubsetMatches(a)){this.handleResponse(a, +c,{query:a});return}if(this.dataSource.responseStripAfter)this.dataSource.doBeforeParseData=this.preparseRawResponse;if(this.applyLocalFilter)this.dataSource.doBeforeCallback=this.filterResults;c=this.generateRequest(a);if(c!==void 0){this.dataRequestEvent.fire(this,a,c);this.dataSource.sendRequest(c,{success:this.handleResponse,failure:this.handleResponse,scope:this,argument:{query:a}})}else this.dataRequestCancelEvent.fire(this,a)}}}; +YAHOO.widget.AutoComplete.prototype._populateListItem=function(a,c,b){a.innerHTML=this.formatResult(c,b,a._sResultMatch)}; +YAHOO.widget.AutoComplete.prototype._populateList=function(a,c,b){this._nTypeAheadDelayID!=-1&&clearTimeout(this._nTypeAheadDelayID);a=b&&b.query?b.query:a;if((b=this.doBeforeLoadData(a,c,b))&&!c.error){this.dataReturnEvent.fire(this,a,c.results);if(this._bFocused){var d=decodeURIComponent(a);this._sCurQuery=d;this._bItemSelected=false;var c=c.results,b=Math.min(c.length,this.maxResultsDisplayed),f=this.dataSource.responseSchema.fields?this.dataSource.responseSchema.fields[0].key||this.dataSource.responseSchema.fields[0]: +0;if(b>0){(!this._elList||this._elList.childNodes.length=0;j--){var h=g[j],e=c[j];if(this.resultTypeList){var i=[];i[0]=YAHOO.lang.isString(e)?e:e[f]||e[this.key];var k=this.dataSource.responseSchema.fields;if(YAHOO.lang.isArray(k)&&k.length>1)for(var l=1,q=k.length;l=b;f--){d=g[f];d.style.display="none"}this._nDisplayedItems=b;this.containerPopulateEvent.fire(this,a,c);if(this.autoHighlight){b=this._elList.firstChild;this._toggleHighlight(b,"to");this.itemArrowToEvent.fire(this,b);this._typeAhead(b,a)}else this._toggleHighlight(this._elCurListItem,"from");b=this._doBeforeExpandContainer(this._elTextbox,this._elContainer,a,c); +this._toggleContainer(b)}else this._toggleContainer(false)}}else this.dataErrorEvent.fire(this,a,c)};YAHOO.widget.AutoComplete.prototype._doBeforeExpandContainer=function(a,c,b,d){this.autoSnapContainer&&this.snapContainer();return this.doBeforeExpandContainer(a,c,b,d)}; +YAHOO.widget.AutoComplete.prototype._clearSelection=function(){var a=this.delimChar?this._extractQuery(this._elTextbox.value):{previous:"",query:this._elTextbox.value};this._elTextbox.value=a.previous;this.selectionEnforceEvent.fire(this,a.query)};YAHOO.widget.AutoComplete.prototype._textMatchesOption=function(){for(var a=null,c=0;c=0;f--){d=a.lastIndexOf(c[f]);d>b&&(b=d)}if(c[f]==" ")for(d=c.length-1;d>=0;d--)if(a[b-1]==c[d]){b--;break}if(b>-1){for(c=b+1;a.charAt(c)==" ";)c=c+1;b=a.substring(0,c);a=a.substr(c)}else b="";return{previous:b,query:a}}; +YAHOO.widget.AutoComplete.prototype._toggleContainerHelpers=function(a){var c=this._elContent.offsetWidth+"px",b=this._elContent.offsetHeight+"px";if(this.useIFrame&&this._elIFrame){var d=this._elIFrame;if(a){d.style.width=c;d.style.height=b;d.style.padding=""}else{d.style.width=0;d.style.height=0;d.style.padding=0}}if(this.useShadow&&this._elShadow){d=this._elShadow;if(a){d.style.width=c;d.style.height=b}else{d.style.width=0;d.style.height=0}}}; +YAHOO.widget.AutoComplete.prototype._toggleContainer=function(a){var c=this._elContainer;if(!this.alwaysShowContainer||!this._bContainerOpen){if(!a){this._toggleHighlight(this._elCurListItem,"from");this._nDisplayedItems=0;this._sCurQuery=null;if(this._elContent.style.display=="none")return}var b=this._oAnim;if(b&&b.getEl()&&(this.animHoriz||this.animVert)){b.isAnimated()&&b.stop(true);var d=this._elContent.cloneNode(true);c.appendChild(d);d.style.top="-9000px";d.style.width="";d.style.height=""; +d.style.display="";var f=d.offsetWidth,g=d.offsetHeight,j=this.animHoriz?0:f,h=this.animVert?0:g;b.attributes=a?{width:{to:f},height:{to:g}}:{width:{to:j},height:{to:h}};if(a&&!this._bContainerOpen){this._elContent.style.width=j+"px";this._elContent.style.height=h+"px"}else{this._elContent.style.width=f+"px";this._elContent.style.height=g+"px"}c.removeChild(d);var d=null,e=this;this._toggleContainerHelpers(false);this._elContent.style.display="";b.onComplete.subscribe(function(){b.onComplete.unsubscribeAll(); +if(a){e._toggleContainerHelpers(true);e._bContainerOpen=a;e.containerExpandEvent.fire(e)}else{e._elContent.style.display="none";e._bContainerOpen=a;e.containerCollapseEvent.fire(e)}});b.animate()}else if(a){this._elContent.style.display="";this._toggleContainerHelpers(true);this._bContainerOpen=a;this.containerExpandEvent.fire(this)}else{this._toggleContainerHelpers(false);this._elContent.style.display="none";this._bContainerOpen=a;this.containerCollapseEvent.fire(this)}}}; +YAHOO.widget.AutoComplete.prototype._toggleHighlight=function(a,c){if(a){var b=this.highlightClassName;if(this._elCurListItem){YAHOO.util.Dom.removeClass(this._elCurListItem,b);this._elCurListItem=null}if(c=="to"&&b){YAHOO.util.Dom.addClass(a,b);this._elCurListItem=a}}}; +YAHOO.widget.AutoComplete.prototype._togglePrehighlight=function(a,c){var b=this.prehighlightClassName;this._elCurPrehighlightItem&&YAHOO.util.Dom.removeClass(this._elCurPrehighlightItem,b);if(a!=this._elCurListItem)if(c=="mouseover"&&b){YAHOO.util.Dom.addClass(a,b);this._elCurPrehighlightItem=a}else YAHOO.util.Dom.removeClass(a,b)}; +YAHOO.widget.AutoComplete.prototype._updateValue=function(a){if(!this.suppressInputUpdate){var c=this._elTextbox,b=this.delimChar?this.delimChar[0]||this.delimChar:null,d=a._sResultMatch,f="";if(b){f=this._sPastSelections;f=f+(d+b);b!=" "&&(f=f+" ")}else f=d;c.value=f;if(c.type=="textarea")c.scrollTop=c.scrollHeight;b=c.value.length;this._selectText(c,b,b);this._elCurListItem=a}}; +YAHOO.widget.AutoComplete.prototype._selectItem=function(a){this._bItemSelected=true;this._updateValue(a);this._sPastSelections=this._elTextbox.value;this._clearInterval();this.itemSelectEvent.fire(this,a,a._oResultData);this._toggleContainer(false)};YAHOO.widget.AutoComplete.prototype._jumpSelection=function(){this._elCurListItem?this._selectItem(this._elCurListItem):this._toggleContainer(false)}; +YAHOO.widget.AutoComplete.prototype._moveSelection=function(a){if(this._bContainerOpen){var c=this._elCurListItem,b=-1;if(c)b=c._nItemIndex;b=a==40?b+1:b-1;if(!(b<-2||b>=this._nDisplayedItems)){if(c){this._toggleHighlight(c,"from");this.itemArrowFromEvent.fire(this,c)}if(b==-1)this._elTextbox.value=this.delimChar?this._sPastSelections+this._sCurQuery:this._sCurQuery;else if(b==-2)this._toggleContainer(false);else{var c=this._elList.childNodes[b],d=this._elContent,f=YAHOO.util.Dom.getStyle(d,"overflow"), +g=YAHOO.util.Dom.getStyle(d,"overflowY");if((f=="auto"||f=="scroll"||g=="auto"||g=="scroll")&&b>-1&&bd.scrollTop+d.offsetHeight)d.scrollTop=c.offsetTop+c.offsetHeight-d.offsetHeight;else{if(c.offsetTop+c.offsetHeightd.scrollTop+d.offsetHeight)this._elContent.scrollTop=c.offsetTop+c.offsetHeight-d.offsetHeight;this._toggleHighlight(c, +"to");this.itemArrowToEvent.fire(this,c);if(this.typeAhead){this._updateValue(c);this._sCurQuery=c._sResultMatch}}}}}; +YAHOO.widget.AutoComplete.prototype._onContainerMouseover=function(a,c){for(var b=YAHOO.util.Event.getTarget(a),d=b.nodeName.toLowerCase();b&&d!="table";){switch(d){case "body":return;case "li":c.prehighlightClassName?c._togglePrehighlight(b,"mouseover"):c._toggleHighlight(b,"to");c.itemMouseOverEvent.fire(c,b);break;case "div":if(YAHOO.util.Dom.hasClass(b,"yui-ac-container")){c._bOverContainer=true;return}}(b=b.parentNode)&&(d=b.nodeName.toLowerCase())}}; +YAHOO.widget.AutoComplete.prototype._onContainerMouseout=function(a,c){for(var b=YAHOO.util.Event.getTarget(a),d=b.nodeName.toLowerCase();b&&d!="table";){switch(d){case "body":return;case "li":c.prehighlightClassName?c._togglePrehighlight(b,"mouseout"):c._toggleHighlight(b,"from");c.itemMouseOutEvent.fire(c,b);break;case "ul":c._toggleHighlight(c._elCurListItem,"to");break;case "div":if(YAHOO.util.Dom.hasClass(b,"yui-ac-container")){c._bOverContainer=false;return}}(b=b.parentNode)&&(d=b.nodeName.toLowerCase())}}; +YAHOO.widget.AutoComplete.prototype._onContainerClick=function(a,c){for(var b=YAHOO.util.Event.getTarget(a),d=b.nodeName.toLowerCase();b&&d!="table";){switch(d){case "body":return;case "li":c._toggleHighlight(b,"to");c._selectItem(b);return}(b=b.parentNode)&&(d=b.nodeName.toLowerCase())}};YAHOO.widget.AutoComplete.prototype._onContainerScroll=function(a,c){c._focus()};YAHOO.widget.AutoComplete.prototype._onContainerResize=function(a,c){c._toggleContainerHelpers(c._bContainerOpen)}; +YAHOO.widget.AutoComplete.prototype._onTextboxKeyDown=function(a,c){var b=a.keyCode;c._nTypeAheadDelayID!=-1&&clearTimeout(c._nTypeAheadDelayID);switch(b){case 9:if(!YAHOO.env.ua.opera&&navigator.userAgent.toLowerCase().indexOf("mac")==-1||YAHOO.env.ua.webkit>420)if(c._elCurListItem){c.delimChar&&c._nKeyCode!=b&&c._bContainerOpen&&YAHOO.util.Event.stopEvent(a);c._selectItem(c._elCurListItem)}else c._toggleContainer(false);break;case 13:if(!YAHOO.env.ua.opera&&navigator.userAgent.toLowerCase().indexOf("mac")== +-1||YAHOO.env.ua.webkit>420)if(c._elCurListItem){c._nKeyCode!=b&&c._bContainerOpen&&YAHOO.util.Event.stopEvent(a);c._selectItem(c._elCurListItem)}else c._toggleContainer(false);break;case 27:c._toggleContainer(false);return;case 39:c._jumpSelection();break;case 38:if(c._bContainerOpen){YAHOO.util.Event.stopEvent(a);c._moveSelection(b)}break;case 40:if(c._bContainerOpen){YAHOO.util.Event.stopEvent(a);c._moveSelection(b)}break;default:c._bItemSelected=false;c._toggleHighlight(c._elCurListItem,"from"); +c.textboxKeyEvent.fire(c,b)}b===18&&c._enableIntervalDetection();c._nKeyCode=b}; +YAHOO.widget.AutoComplete.prototype._onTextboxKeyPress=function(a,c){var b=a.keyCode;if(YAHOO.env.ua.opera||navigator.userAgent.toLowerCase().indexOf("mac")!=-1&&YAHOO.env.ua.webkit<420)switch(b){case 9:if(c._bContainerOpen){c.delimChar&&YAHOO.util.Event.stopEvent(a);c._elCurListItem?c._selectItem(c._elCurListItem):c._toggleContainer(false)}break;case 13:if(c._bContainerOpen){YAHOO.util.Event.stopEvent(a);c._elCurListItem?c._selectItem(c._elCurListItem):c._toggleContainer(false)}}else b==229&&c._enableIntervalDetection()}; +YAHOO.widget.AutoComplete.prototype._onTextboxKeyUp=function(a,c){var b=this.value;c._initProps();if(!c._isIgnoreKey(a.keyCode)){c._nDelayID!=-1&&clearTimeout(c._nDelayID);c._nDelayID=setTimeout(function(){c._sendQuery(b)},c.queryDelay*1E3);c.textboxKeyUpEvent.fire(c,b)}};YAHOO.widget.AutoComplete.prototype._onTextboxFocus=function(a,c){if(!c._bFocused){c._elTextbox.setAttribute("autocomplete","off");c._bFocused=true;c._sInitInputValue=c._elTextbox.value;c.textboxFocusEvent.fire(c)}}; +YAHOO.widget.AutoComplete.prototype._onTextboxBlur=function(a,c){if(!c._bOverContainer||c._nKeyCode==9){if(!c._bItemSelected){var b=c._textMatchesOption();!c._bContainerOpen||c._bContainerOpen&&b===null?c.forceSelection?c._clearSelection():c.unmatchedItemSelectEvent.fire(c,c._sCurQuery):c.forceSelection&&c._selectItem(b)}c._clearInterval();c._bFocused=false;c._sInitInputValue!==c._elTextbox.value&&c.textboxChangeEvent.fire(c);c.textboxBlurEvent.fire(c);c._toggleContainer(false)}else c._focus()}; +YAHOO.widget.AutoComplete.prototype._onWindowUnload=function(a,c){c&&(c._elTextbox&&c.allowBrowserAutocomplete)&&c._elTextbox.setAttribute("autocomplete","on")};YAHOO.widget.AutoComplete.prototype.doBeforeSendQuery=function(a){return this.generateRequest(a)};YAHOO.widget.AutoComplete.prototype.getListItems=function(){for(var a=[],c=this._elList.childNodes,b=c.length-1;b>=0;b--)a[b]=c[b];return a}; +YAHOO.widget.AutoComplete._cloneObject=function(a){if(!YAHOO.lang.isValue(a))return a;var c={};if(YAHOO.lang.isFunction(a))c=a;else if(YAHOO.lang.isArray(a))for(var c=[],b=0,d=a.length;b0){h=j-1;do if((j=a.subscribers[h])&&j.obj==c&&j.fn==b)return true;while(h--)}return false};YAHOO.lang.augmentProto(b,YAHOO.util.EventProvider)})(); +(function(){function a(){if(!k){k=document.createElement("div");k.innerHTML='
    ';l=k.firstChild;q=l.nextSibling;p=q.nextSibling}return k}function c(){l||a();return l.cloneNode(false)}function b(){q||a();return q.cloneNode(false)}function d(){p||a();return p.cloneNode(false)}YAHOO.widget.Module=function(a,e){a&&this.init(a,e)};var f=YAHOO.util.Dom,g=YAHOO.util.Config,j=YAHOO.util.Event,h=YAHOO.util.CustomEvent, +e=YAHOO.widget.Module,i=YAHOO.env.ua,k,l,q,p,n=YAHOO.lang.isBoolean,o=["visible"];e.IMG_ROOT=null;e.IMG_ROOT_SSL=null;e.CSS_MODULE="yui-module";e.CSS_HEADER="hd";e.CSS_BODY="bd";e.CSS_FOOTER="ft";e.RESIZE_MONITOR_SECURE_URL="javascript:false;";e.RESIZE_MONITOR_BUFFER=1;e.textResizeEvent=new h("textResize");e.forceDocumentRedraw=function(){var a=document.documentElement;if(a){a.className=a.className+" ";a.className=YAHOO.lang.trim(a.className)}};var m=e,r=e,s=e.IMG_ROOT,t=function(){var a=navigator.userAgent.toLowerCase(); +return a.indexOf("windows")!=-1||a.indexOf("win32")!=-1?"windows":a.indexOf("macintosh")!=-1?"mac":false}(),u=function(){var a=navigator.userAgent.toLowerCase();return a.indexOf("opera")!=-1?"opera":a.indexOf("msie 7")!=-1?"ie7":a.indexOf("msie")!=-1?"ie":a.indexOf("safari")!=-1?"safari":a.indexOf("gecko")!=-1?"gecko":false}(),w;w=window.location.href.toLowerCase().indexOf("https")===0?true:false;m.prototype={constructor:r,element:null,header:null,body:null,footer:null,id:null,imageRoot:s,initEvents:function(){var a= +h.LIST;this.beforeInitEvent=this.createEvent("beforeInit");this.beforeInitEvent.signature=a;this.initEvent=this.createEvent("init");this.initEvent.signature=a;this.appendEvent=this.createEvent("append");this.appendEvent.signature=a;this.beforeRenderEvent=this.createEvent("beforeRender");this.beforeRenderEvent.signature=a;this.renderEvent=this.createEvent("render");this.renderEvent.signature=a;this.changeHeaderEvent=this.createEvent("changeHeader");this.changeHeaderEvent.signature=a;this.changeBodyEvent= +this.createEvent("changeBody");this.changeBodyEvent.signature=a;this.changeFooterEvent=this.createEvent("changeFooter");this.changeFooterEvent.signature=a;this.changeContentEvent=this.createEvent("changeContent");this.changeContentEvent.signature=a;this.destroyEvent=this.createEvent("destroy");this.destroyEvent.signature=a;this.beforeShowEvent=this.createEvent("beforeShow");this.beforeShowEvent.signature=a;this.showEvent=this.createEvent("show");this.showEvent.signature=a;this.beforeHideEvent=this.createEvent("beforeHide"); +this.beforeHideEvent.signature=a;this.hideEvent=this.createEvent("hide");this.hideEvent.signature=a},platform:t,browser:u,isSecure:w,initDefaultConfig:function(){this.cfg.addProperty("visible",{handler:this.configVisible,value:true,validator:n});this.cfg.addProperty("effect",{handler:this.configEffect,suppressEvent:true,supercedes:o});this.cfg.addProperty("monitorresize",{handler:this.configMonitorResize,value:true});this.cfg.addProperty("appendtodocumentbody",{value:false})},init:function(b,i){var c; +this.initEvents();this.beforeInitEvent.fire(e);this.cfg=new g(this);if(this.isSecure)this.imageRoot=e.IMG_ROOT_SSL;if(typeof b=="string"){c=b;b=document.getElementById(b);if(!b){b=a().cloneNode(false);b.id=c}}this.id=f.generateId(b);this.element=b;if(c=this.element.firstChild){var d=false,k=false,l=false;do if(1==c.nodeType)if(!d&&f.hasClass(c,e.CSS_HEADER)){this.header=c;d=true}else if(!k&&f.hasClass(c,e.CSS_BODY)){this.body=c;k=true}else if(!l&&f.hasClass(c,e.CSS_FOOTER)){this.footer=c;l=true}while(c= +c.nextSibling)}this.initDefaultConfig();f.addClass(this.element,e.CSS_MODULE);i&&this.cfg.applyConfig(i,true);g.alreadySubscribed(this.renderEvent,this.cfg.fireQueue,this.cfg)||this.renderEvent.subscribe(this.cfg.fireQueue,this.cfg,true);this.initEvent.fire(e)},initResizeMonitor:function(){if(i.gecko&&this.platform=="windows"){var a=this;setTimeout(function(){a._initResizeMonitor()},0)}else this._initResizeMonitor()},_initResizeMonitor:function(){function a(){e.textResizeEvent.fire()}var b,c;if(!i.opera){c= +f.get("_yuiResizeMonitor");var d=this._supportsCWResize();if(!c){c=document.createElement("iframe");if(this.isSecure&&e.RESIZE_MONITOR_SECURE_URL&&i.ie)c.src=e.RESIZE_MONITOR_SECURE_URL;if(!d)c.src="data:text/html;charset=utf-8,"+encodeURIComponent(' diff --git a/rhodecode/templates/admin/notifications/notifications_data.html b/rhodecode/templates/admin/notifications/notifications_data.html --- a/rhodecode/templates/admin/notifications/notifications_data.html +++ b/rhodecode/templates/admin/notifications/notifications_data.html @@ -3,18 +3,14 @@ <% unread = lambda n:{False:'unread'}.get(n) %> -
    -
    - ${c.notifications.pager('$link_previous ~2~ $link_next')} -
    -
    +
    @@ -42,6 +42,15 @@ ${_('Type of repository to create.')}
    +
    +
    + +
    +
    + ${h.select('landing_rev','',c.landing_revs,class_="medium")} + ${_('Default revision for files page, downloads, whoosh and readme')} +
    +
    @@ -61,7 +70,7 @@
    - ${h.submit('add',_('add'),class_="ui-button")} + ${h.submit('add',_('add'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/repos/repo_edit.html b/rhodecode/templates/admin/repos/repo_edit.html --- a/rhodecode/templates/admin/repos/repo_edit.html +++ b/rhodecode/templates/admin/repos/repo_edit.html @@ -62,6 +62,15 @@
    +
    + +
    +
    + ${h.select('landing_rev','',c.landing_revs,class_="medium")} + ${_('Default revision for files page, downloads, whoosh and readme')} +
    +
    +
    @@ -99,6 +108,15 @@
    +
    + +
    +
    + ${h.checkbox('enable_locking',value="True")} + ${_('Enable lock-by-pulling on repository.')} +
    +
    +
    @@ -120,8 +138,8 @@
    - ${h.submit('save','Save',class_="ui-button")} - ${h.reset('reset','Reset',class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    @@ -187,13 +205,49 @@
      -
    • ${_('''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')}
    ${h.end_form()} +

    ${_('Locking')}

    + ${h.form(url('repo_locking', repo_name=c.repo_info.repo_name),method='put')} +
    +
    + %if c.repo_info.locked[0]: + ${h.submit('set_unlock' ,_('Unlock locked repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to unlock repository')+"');")} + ${'Locked by %s on %s' % (h.person_by_id(c.repo_info.locked[0]),h.fmt_date(h.time_to_datetime(c.repo_info.locked[1])))} + %else: + ${h.submit('set_lock',_('lock repo'),class_="ui-btn",onclick="return confirm('"+_('Confirm to lock repository')+"');")} + ${_('Repository is not locked')} + %endif +
    +
    +
      +
    • ${_('Force locking on repository. Works only when anonymous access is disabled')} +
    • +
    +
    +
    + ${h.end_form()} + +

    ${_('Set as fork of')}

    + ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')} +
    +
    + ${h.select('id_fork_of','',c.repos_list,class_="medium")} + ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)} +
    +
    +
      +
    • ${_('''Manually set this repository as a fork of another from the list''')}
    • +
    +
    +
    + ${h.end_form()} +

    ${_('Delete')}

    ${h.form(url('repo', repo_name=c.repo_info.repo_name),method='delete')}
    @@ -208,24 +262,7 @@
    - ${h.end_form()} - -

    ${_('Set as fork')}

    - ${h.form(url('repo_as_fork', repo_name=c.repo_info.repo_name),method='put')} -
    -
    - ${h.select('id_fork_of','',c.repos_list,class_="medium")} - ${h.submit('set_as_fork_%s' % c.repo_info.repo_name,_('set'),class_="ui-btn",)} -
    -
    -
      -
    • ${_('''Manually set this repository as a fork of another''')}
    • -
    -
    -
    - ${h.end_form()} - + ${h.end_form()} - diff --git a/rhodecode/templates/admin/repos/repo_edit_perms.html b/rhodecode/templates/admin/repos/repo_edit_perms.html --- a/rhodecode/templates/admin/repos/repo_edit_perms.html +++ b/rhodecode/templates/admin/repos/repo_edit_perms.html @@ -16,7 +16,7 @@ ${_('private repository')} -
    %for notification in c.notifications:
    - gravatar + gravatar
    ${notification.notification.description} @@ -22,6 +18,11 @@ unread = lambda n:{False:'unread'}.get(n
    + %if not notification.read: +
    + +
    + %endif
    ${h.literal(notification.notification.subject)}
    @@ -30,7 +31,7 @@ unread = lambda n:{False:'unread'}.get(n
    - ${c.notifications.pager('$link_previous ~2~ $link_next')} + ${c.notifications.pager('$link_previous ~2~ $link_next',**request.GET.mixed())}
    diff --git a/rhodecode/templates/admin/notifications/show_notification.html b/rhodecode/templates/admin/notifications/show_notification.html --- a/rhodecode/templates/admin/notifications/show_notification.html +++ b/rhodecode/templates/admin/notifications/show_notification.html @@ -30,7 +30,7 @@
    - gravatar + gravatar
    ${c.notification.description} diff --git a/rhodecode/templates/admin/permissions/permissions.html b/rhodecode/templates/admin/permissions/permissions.html --- a/rhodecode/templates/admin/permissions/permissions.html +++ b/rhodecode/templates/admin/permissions/permissions.html @@ -37,7 +37,7 @@
    -
    +
    @@ -66,9 +66,16 @@ ${h.select('default_create','',c.create_choices)}
    - +
    +
    + +
    +
    + ${h.select('default_fork','',c.fork_choices)} +
    +
    - ${h.submit('set',_('set'),class_="ui-button")} + ${h.submit('set',_('set'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/repos/repo_add_base.html b/rhodecode/templates/admin/repos/repo_add_base.html --- a/rhodecode/templates/admin/repos/repo_add_base.html +++ b/rhodecode/templates/admin/repos/repo_add_base.html @@ -30,7 +30,7 @@
    ${h.select('repo_group',request.GET.get('parent_group'),c.repo_groups,class_="medium")} - ${_('Optional select a group to put this repository into.')} + ${_('Optionaly select a group to put this repository into.')}
    + %else: @@ -25,7 +25,7 @@ %endfor - - - - - - - - + <% + _tmpl = h.literal("""' \ + \ + \ + \ + \ + \ + '""") + %> + ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' + %endfor - - - - - - - - +<% + _tmpl = h.literal("""' \ + \ + \ + \ + \ + \ + '""") + %> + ## ADD HERE DYNAMICALLY NEW INPUTS FROM THE '_tmpl' + diff --git a/rhodecode/templates/admin/settings/hooks.html b/rhodecode/templates/admin/settings/hooks.html --- a/rhodecode/templates/admin/settings/hooks.html +++ b/rhodecode/templates/admin/settings/hooks.html @@ -70,7 +70,7 @@
    - ${h.submit('save',_('Save'),class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/settings/settings.html b/rhodecode/templates/admin/settings/settings.html --- a/rhodecode/templates/admin/settings/settings.html +++ b/rhodecode/templates/admin/settings/settings.html @@ -38,11 +38,12 @@ ${_('destroy old data')} + ${_('Rescan repositories location for new repositories. Also deletes obsolete if `destroy` flag is checked ')}
    - ${h.submit('rescan',_('Rescan repositories'),class_="ui-button")} + ${h.submit('rescan',_('Rescan repositories'),class_="ui-btn large")}
    @@ -67,7 +68,7 @@
    - ${h.submit('reindex',_('Reindex'),class_="ui-button")} + ${h.submit('reindex',_('Reindex'),class_="ui-btn large")}
    @@ -108,15 +109,72 @@
    - ${h.submit('save',_('Save settings'),class_="ui-button")} - ${h.reset('reset',_('Reset'),class_="ui-button")} + ${h.submit('save',_('Save settings'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    ${h.end_form()} -

    ${_('Mercurial settings')}

    - ${h.form(url('admin_setting', setting_id='mercurial'),method='put')} +

    ${_('Visualisation settings')}

    + ${h.form(url('admin_setting', setting_id='visual'),method='put')} +
    + + +
    + +
    +
    + +
    +
    +
    + ${h.checkbox('rhodecode_show_public_icon','True')} + +
    +
    + ${h.checkbox('rhodecode_show_private_icon','True')} + +
    +
    +
    + +
    +
    + +
    +
    +
    + ${h.checkbox('rhodecode_stylify_metatags','True')} + +
    +
    +
      +
    • [featured] featured
    • +
    • [stale] stale
    • +
    • [dead] dead
    • +
    • [lang => lang] lang
    • +
    • [license => License] License
    • +
    • [requires => Repo] requires => Repo
    • +
    • [recommends => Repo] recommends => Repo
    • +
    • [see => URI] see => URI
    • +
    +
    +
    +
    + +
    + ${h.submit('save',_('Save settings'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")} +
    + +
    +
    + ${h.end_form()} + + +

    ${_('VCS settings')}

    + ${h.form(url('admin_setting', setting_id='vcs'),method='put')}
    @@ -129,8 +187,9 @@
    ${h.checkbox('web_push_ssl','true')} - +
    + ${_('RhodeCode will require SSL for pushing or pulling. If SSL is missing it will return HTTP Error 406: Not Acceptable')}
    @@ -148,18 +207,39 @@
    - ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')} - + ${h.checkbox('hooks_changegroup_push_logger','True')} +
    - ${h.checkbox('hooks_preoutgoing_pull_logger','True')} - + ${h.checkbox('hooks_outgoing_pull_logger','True')} +
    ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
    +
    +
    + +
    +
    +
    + ${h.checkbox('extensions_largefiles','True')} + +
    +
    + ${h.checkbox('extensions_hgsubversion','True')} + +
    + ${_('Requires hgsubversion library installed. Allows clonning from svn remote locations')} + ##
    + ## ${h.checkbox('extensions_hggit','True')} + ## + ##
    + ##${_('Requires hg-git library installed. Allows clonning from git remote locations')} +
    +
    @@ -169,12 +249,13 @@ ${_('unlock')} + ${_('Location where repositories are stored. After changing this value a restart, and rescan is required')}
    - ${h.submit('save',_('Save settings'),class_="ui-button")} - ${h.reset('reset',_('Reset'),class_="ui-button")} + ${h.submit('save',_('Save settings'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    @@ -204,7 +285,7 @@
    - ${h.submit('send',_('Send'),class_="ui-button")} + ${h.submit('send',_('Send'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/users/user_add.html b/rhodecode/templates/admin/users/user_add.html --- a/rhodecode/templates/admin/users/user_add.html +++ b/rhodecode/templates/admin/users/user_add.html @@ -56,10 +56,10 @@
    - +
    - ${h.text('name',class_='small')} + ${h.text('firstname',class_='small')}
    @@ -91,7 +91,7 @@
    - ${h.submit('save',_('save'),class_="ui-button")} + ${h.submit('save',_('save'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/users/user_edit.html b/rhodecode/templates/admin/users/user_edit.html --- a/rhodecode/templates/admin/users/user_edit.html +++ b/rhodecode/templates/admin/users/user_edit.html @@ -83,10 +83,10 @@
    - +
    - ${h.text('name',class_='medium')} + ${h.text('firstname',class_='medium')}
    @@ -126,14 +126,14 @@
    - ${h.submit('save',_('Save'),class_="ui-button")} - ${h.reset('reset',_('Reset'),class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    ${h.end_form()} -
    +
    ${_('Permissions')}
    @@ -144,15 +144,138 @@
    + +
    +
    + ${h.checkbox('inherit_default_permissions',value=True)} +
    + ${h.literal(_('Select to inherit permissions from %s settings. ' + 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))} +
    +
    +
    +
    ${h.checkbox('create_repo_perm',value=True)}
    +
    +
    + +
    +
    + ${h.checkbox('fork_repo_perm',value=True)} +
    +
    +
    - ${h.submit('save',_('Save'),class_="ui-button")} - ${h.reset('reset',_('Reset'),class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")} +
    +
    +
    + ${h.end_form()} + + ## permissions overview +
    + %for section in sorted(c.perm_user.permissions.keys()): +
    ${section.replace("_"," ").capitalize()}
    + %if not c.perm_user.permissions[section]: + ${_('Nothing here yet')} + %else: +
    +
    {2}
    {2}
    {0}
    {0}
    {0}
    {0}
    ${r2p.user.username}${_('default')}
    ${h.radio('u_perm_%s' % r2p.user.username,'repository.write')} ${h.radio('u_perm_%s' % r2p.user.username,'repository.admin')} - ${r2p.user.username} + ${r2p.user.username if r2p.user.username != 'default' else _('default')} %if r2p.user.username !='default': @@ -46,7 +46,12 @@ ${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.write')} ${h.radio('g_perm_%s' % g2p.users_group.users_group_name,'repository.admin')} - ${g2p.users_group.users_group_name} + + %if h.HasPermissionAny('hg.admin')(): + ${g2p.users_group.users_group_name} + %else: + ${g2p.users_group.users_group_name} + %endif @@ -55,20 +60,23 @@
    ${h.radio('perm_new_member','repository.none')}${h.radio('perm_new_member','repository.read')}${h.radio('perm_new_member','repository.write')}${h.radio('perm_new_member','repository.admin')} -
    - ${h.text('perm_new_member_name',class_='yui-ac-input')} - ${h.hidden('perm_new_member_type')} -
    -
    -
    \ +
    \ + \ + \ +
    \ +
    \ +
    @@ -113,16 +121,8 @@ YUE.onDOMReady(function () { YUD.setStyle('add_perm_input', 'display', 'none'); } YAHOO.util.Event.addListener('add_perm', 'click', function () { - YUD.setStyle('add_perm_input', 'display', ''); - YUD.setStyle('add_perm', 'opacity', '0.6'); - YUD.setStyle('add_perm', 'cursor', 'default'); + addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n}); }); - MembersAutoComplete( - ${c.users_array|n}, - ${c.users_groups_array|n}, - "${_('Group')}", - "${_('members')}" - ); }); diff --git a/rhodecode/templates/admin/repos/repos.html b/rhodecode/templates/admin/repos/repos.html --- a/rhodecode/templates/admin/repos/repos.html +++ b/rhodecode/templates/admin/repos/repos.html @@ -5,9 +5,8 @@ ${_('Repositories administration')} - ${c.rhodecode_name} - <%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} » ${_('Repositories')} + ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('repositories')} <%def name="page_nav()"> ${self.menu('admin')} @@ -23,102 +22,114 @@ - -
    -
    - <%cnt=0%> - <%namespace name="dt" file="/_data_table/_dt_elements.html"/> - - - - - - - - - - - - - +
    +
    - %for cnt,repo in enumerate(c.repos_list): - - - - ##DESCRIPTION - - ##LAST CHANGE - - ##LAST REVISION - - - - - %endfor -
    ${_('Name')}${_('Description')}${_('Last change')}${_('Tip')}${_('Contact')}${_('Action')}
    - ${dt.quick_menu(repo['name'])} - - ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'), admin=True)} - - ${h.truncate(repo['description'],60)} - - ${h.age(repo['last_change'])} - - ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])} - ${h.person(repo['contact'])} - ${h.form(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()} -
    -
    -
    + + diff --git a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html --- a/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html +++ b/rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html @@ -15,7 +15,7 @@
    ${h.radio('u_perm_%s' % r2p.user.username,'group.write')} ${h.radio('u_perm_%s' % r2p.user.username,'group.admin')} - ${r2p.user.username} + ${r2p.user.username if r2p.user.username != 'default' else _('default')} %if r2p.user.username !='default': @@ -44,20 +44,23 @@
    ${h.radio('perm_new_member','group.none')}${h.radio('perm_new_member','group.read')}${h.radio('perm_new_member','group.write')}${h.radio('perm_new_member','group.admin')} -
    - ${h.text('perm_new_member_name',class_='yui-ac-input')} - ${h.hidden('perm_new_member_type')} -
    -
    -
    \ +
    \ + \ + \ +
    \ +
    \ +
    @@ -102,16 +105,8 @@ YUE.onDOMReady(function () { YUD.setStyle('add_perm_input', 'display', 'none'); } YAHOO.util.Event.addListener('add_perm', 'click', function () { - YUD.setStyle('add_perm_input', 'display', ''); - YUD.setStyle('add_perm', 'opacity', '0.6'); - YUD.setStyle('add_perm', 'cursor', 'default'); + addPermAction(${_tmpl}, ${c.users_array|n}, ${c.users_groups_array|n}); }); - MembersAutoComplete( - ${c.users_array|n}, - ${c.users_groups_array|n}, - "${_('Group')}", - "${_('members')}" - ); }); diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_add.html b/rhodecode/templates/admin/repos_groups/repos_groups_add.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_add.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_add.html @@ -55,7 +55,7 @@
    - ${h.submit('save',_('save'),class_="ui-button")} + ${h.submit('save',_('save'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_edit.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_edit.html @@ -60,11 +60,19 @@
    <%include file="repos_group_edit_perms.html"/>
    - +
    +
    + +
    +
    + ${h.checkbox('enable_locking',value="True")} + ${_('Enable lock-by-pulling on group. This option will be applied to all other groups and repositories inside')} +
    +
    - ${h.submit('save',_('Save'),class_="ui-button")} - ${h.reset('reset',_('Reset'),class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/repos_groups/repos_groups_show.html b/rhodecode/templates/admin/repos_groups/repos_groups_show.html --- a/rhodecode/templates/admin/repos_groups/repos_groups_show.html +++ b/rhodecode/templates/admin/repos_groups/repos_groups_show.html @@ -51,7 +51,7 @@
    ${gr.repositories.count()} ${h.form(url('repos_group', id=gr.group_id),method='delete')} - ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group: %s') % gr.name+"');")} + ${h.submit('remove_%s' % gr.name,_('delete'),class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group: %s') % gr.name+"');")} ${h.end_form()}
    + + + + + + + + %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 + %> + + + + + + %endfor + +
    ${_('Name')}${_('Permission')}${_('Edit Permission')}
    + %if section == 'repositories': + ${k} + %elif section == 'repositories_groups': + ${k} + %else: + ${h.get_permission_name(k)} + %endif + + %if section == 'global': + ${h.bool2icon(k.split('.')[-1] != 'none')} + %else: + ${section_perm} + %endif + + %if section == 'repositories': + ${_('edit')} + %elif section == 'repositories_groups': + ${_('edit')} + %else: + -- + %endif +
    + + %endif + %endfor + + +
    + +
    +
    ${_('Email addresses')}
    +
    + +
    + + %for em in c.user_email_map: + + + + + + %endfor +
    gravatar
    + ${h.form(url('user_emails_delete', id=c.user.user_id),method='delete')} + ${h.hidden('del_email',em.email_id)} + ${h.submit('remove_',_('delete'),id="remove_email_%s" % em.email_id, + class_="delete_icon action_button", onclick="return confirm('"+_('Confirm to delete this email: %s') % em.email+"');")} + ${h.end_form()} +
    +
    + + ${h.form(url('user_emails', id=c.user.user_id),method='put')} +
    + +
    +
    +
    + +
    +
    + ${h.text('new_email', class_='medium')} +
    +
    +
    + ${h.submit('save',_('Add'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/users/user_edit_my_account.html b/rhodecode/templates/admin/users/user_edit_my_account.html --- a/rhodecode/templates/admin/users/user_edit_my_account.html +++ b/rhodecode/templates/admin/users/user_edit_my_account.html @@ -21,158 +21,34 @@ ${self.breadcrumbs()}
    -
    - ${h.form(url('admin_settings_my_account_update'),method='put')} -
    - -
    -
    -
    gravatar
    -

    - %if c.use_gravatar: - ${_('Change your avatar at')} gravatar.com -
    ${_('Using')} ${c.user.email} - %else: -
    ${c.user.email} - %endif -

    -
    -
    -
    -
    - ${c.user.api_key} -
    -
    -
    -
    -
    - -
    -
    - ${h.text('username',class_="medium")} -
    -
    - -
    -
    - -
    -
    - ${h.password('new_password',class_="medium",autocomplete="off")} -
    -
    - -
    -
    - -
    -
    - ${h.password('password_confirmation',class_="medium",autocomplete="off")} -
    -
    - -
    -
    - -
    -
    - ${h.text('name',class_="medium")} -
    -
    - -
    -
    - -
    -
    - ${h.text('lastname',class_="medium")} -
    -
    - -
    -
    - -
    -
    - ${h.text('email',class_="medium")} -
    -
    - -
    - ${h.submit('save',_('Save'),class_="ui-button")} - ${h.reset('reset',_('Reset'),class_="ui-button")} -
    -
    -
    - ${h.end_form()} -
    + ${c.form|n}
    - - ${_('My repos')} / ${_('My permissions')} +
    - %if h.HasPermissionAny('hg.admin','hg.create.repository')(): -
    -
    -
    - - - - - - - - - - - <%namespace name="dt" file="/_data_table/_dt_elements.html"/> - %if c.user_repos: - %for repo in c.user_repos: - - ##QUICK MENU - - ##REPO NAME AND ICONS - - ##LAST REVISION - - - - - %endfor - %else: -
    - ${_('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 -
    - %endif - -
    ${_('Name')}${_('Revision')}${_('Action')}${_('Action')}
    - ${dt.quick_menu(repo['name'])} - - ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))} - - ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])} - ${_('private')} - ${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()} -
    -
    -
    - + +
    + + + diff --git a/rhodecode/templates/admin/users/user_edit_my_account_form.html b/rhodecode/templates/admin/users/user_edit_my_account_form.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/admin/users/user_edit_my_account_form.html @@ -0,0 +1,85 @@ +
    + ${h.form(url('admin_settings_my_account_update'),method='put')} +
    + +
    +
    +
    gravatar
    +

    + %if c.use_gravatar: + ${_('Change your avatar at')} gravatar.com +
    ${_('Using')} ${c.user.email} + %else: +
    ${c.user.email} + %endif +

    +
    +
    +
    +
    + ${c.user.api_key} +
    +
    +
    +
    +
    + +
    +
    + ${h.text('username',class_="medium")} +
    +
    + +
    +
    + +
    +
    + ${h.password('new_password',class_="medium",autocomplete="off")} +
    +
    + +
    +
    + +
    +
    + ${h.password('password_confirmation',class_="medium",autocomplete="off")} +
    +
    + +
    +
    + +
    +
    + ${h.text('firstname',class_="medium")} +
    +
    + +
    +
    + +
    +
    + ${h.text('lastname',class_="medium")} +
    +
    + +
    +
    + +
    +
    + ${h.text('email',class_="medium")} +
    +
    + +
    + ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")} +
    +
    +
    + ${h.end_form()} +
    diff --git a/rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html b/rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html @@ -0,0 +1,41 @@ + +
    ${_('Opened by me')}
    +
      + %if c.my_pull_requests: + %for pull_request in c.my_pull_requests: +
    • +
      + +
      + ${h.form(url('pullrequest_delete', repo_name=pull_request.other_repo.repo_name, pull_request_id=pull_request.pull_request_id),method='delete')} + ${h.submit('remove_%s' % pull_request.pull_request_id,'',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")} + ${h.end_form()} +
      +
      +
    • + %endfor + %else: +
    • ${_('Nothing here yet')}
    • + %endif +
    + +
    ${_('I participate in')}
    + diff --git a/rhodecode/templates/admin/users/user_edit_my_account_repos.html b/rhodecode/templates/admin/users/user_edit_my_account_repos.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/admin/users/user_edit_my_account_repos.html @@ -0,0 +1,46 @@ +
    + + + + + + + + + + + <%namespace name="dt" file="/data_table/_dt_elements.html"/> + %if c.user_repos: + %for repo in c.user_repos: + + ##QUICK MENU + + ##REPO NAME AND ICONS + + ##LAST REVISION + + + + + %endfor + %else: +
    + ${_('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 +
    + %endif + +
    ${_('Name')}${_('Revision')}${_('Action')}${_('Action')}
    + ${dt.quick_menu(repo['name'])} + + ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'))} + + ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])} + ${_('private')} + ${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()} +
    +
    diff --git a/rhodecode/templates/admin/users/users.html b/rhodecode/templates/admin/users/users.html --- a/rhodecode/templates/admin/users/users.html +++ b/rhodecode/templates/admin/users/users.html @@ -6,7 +6,7 @@ <%def name="breadcrumbs_links()"> - ${h.link_to(_('Admin'),h.url('admin_home'))} » ${_('Users')} + ${h.link_to(_('Admin'),h.url('admin_home'))} » 0 ${_('users')} <%def name="page_nav()"> @@ -22,44 +22,126 @@
  • ${h.link_to(_(u'ADD NEW USER'),h.url('new_user'))}
  • - -
    - - - - - - - - - - - - - %for cnt,user in enumerate(c.users_list): - %if user.name !='default': - - - - - - - - - - - - %endif - %endfor -
    ${_('username')}${_('name')}${_('lastname')}${_('last login')}${_('active')}${_('admin')}${_('ldap')}${_('action')}
    gravatar
    ${h.link_to(user.username,h.url('edit_user', id=user.user_id))}${user.name}${user.lastname}${user.last_login}${h.bool2icon(user.active)}${h.bool2icon(user.admin)}${h.bool2icon(bool(user.ldap_dn))} - ${h.form(url('delete_user', id=user.user_id),method='delete')} - ${h.submit('remove_',_('delete'),id="remove_user_%s" % user.user_id, - class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user: %s') % user.username+"');")} - ${h.end_form()} -
    -
    +
    +
    + + + diff --git a/rhodecode/templates/admin/users_groups/users_group_add.html b/rhodecode/templates/admin/users_groups/users_group_add.html --- a/rhodecode/templates/admin/users_groups/users_group_add.html +++ b/rhodecode/templates/admin/users_groups/users_group_add.html @@ -46,7 +46,7 @@
    - ${h.submit('save',_('save'),class_="ui-button")} + ${h.submit('save',_('save'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/admin/users_groups/users_group_edit.html b/rhodecode/templates/admin/users_groups/users_group_edit.html --- a/rhodecode/templates/admin/users_groups/users_group_edit.html +++ b/rhodecode/templates/admin/users_groups/users_group_edit.html @@ -87,7 +87,7 @@
    - ${h.submit('save',_('save'),class_="ui-button")} + ${h.submit('save',_('save'),class_="ui-btn large")}
    @@ -105,15 +105,35 @@
    + +
    +
    + ${h.checkbox('inherit_default_permissions',value=True)} +
    + ${h.literal(_('Select to inherit permissions from %s settings. ' + 'With this selected below options does not have any action') % h.link_to('default', url('edit_permission', id='default')))} +
    +
    +
    +
    ${h.checkbox('create_repo_perm',value=True)}
    +
    +
    + +
    +
    + ${h.checkbox('fork_repo_perm',value=True)} +
    +
    +
    - ${h.submit('save',_('Save'),class_="ui-button")} - ${h.reset('reset',_('Reset'),class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    @@ -140,141 +160,6 @@ diff --git a/rhodecode/templates/admin/users_groups/users_groups.html b/rhodecode/templates/admin/users_groups/users_groups.html --- a/rhodecode/templates/admin/users_groups/users_groups.html +++ b/rhodecode/templates/admin/users_groups/users_groups.html @@ -37,11 +37,11 @@ %for cnt,u_group in enumerate(c.users_groups_list): ${h.link_to(u_group.users_group_name,h.url('edit_users_group', id=u_group.users_group_id))} - ${len(u_group.members)} + ${len(u_group.members)} ${h.bool2icon(u_group.users_group_active)} ${h.form(url('users_group', id=u_group.users_group_id),method='delete')} - ${h.submit('remove_','delete',id="remove_group_%s" % u_group.users_group_id, + ${h.submit('remove_',_('delete'),id="remove_group_%s" % u_group.users_group_id, class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this users group: %s') % u_group.users_group_name+"');")} ${h.end_form()} diff --git a/rhodecode/templates/base/base.html b/rhodecode/templates/base/base.html --- a/rhodecode/templates/base/base.html +++ b/rhodecode/templates/base/base.html @@ -207,6 +207,9 @@ %endif %endif
  • ${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}
  • + %if h.is_hg(c.rhodecode_repo): +
  • ${h.link_to(_('Open new pull request'),h.url('pullrequest_home',repo_name=c.repo_name),class_='pull_request')}
  • + %endif
  • ${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}
  • % if h.HasPermissionAll('hg.admin')('access admin main page'): @@ -247,6 +250,14 @@ ${c.repository_forks} +
  • + + + ${_('Pull requests')} + + ${c.repository_pull_requests} + +
  • ${usermenu()} + ## EXTRA FOR JS ${self.js_extra()} ## diff block <%namespace name="diff_block" file="/changeset/diff_block.html"/> ${diff_block.diff_block(c.changes)} ## template for inline comment form <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> - ${comment.comment_inline_form(c.changeset)} + ${comment.comment_inline_form()} + + ## render comments and inlines + ${comment.generate_comments()} - ## render comments - ${comment.comments(c.changeset)} + ## main comment form and it status + ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id), + h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))} + + ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS diff --git a/rhodecode/templates/changeset/changeset_range.html b/rhodecode/templates/changeset/changeset_range.html --- a/rhodecode/templates/changeset/changeset_range.html +++ b/rhodecode/templates/changeset/changeset_range.html @@ -2,11 +2,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)} - ${c.rhodecode_name} + ${_('%s Changesets') % c.repo_name} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » @@ -35,12 +35,17 @@
    - %for cs in c.cs_ranges: + %for cnt,cs in enumerate(c.cs_ranges): - + + %endfor @@ -49,7 +54,7 @@
    ${_('Files affected')}
    %for cs in c.cs_ranges: -
    r${cs}
    +
    ${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
    %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
    ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID(cs.raw_id,filenode.path)))}
    %endfor @@ -63,7 +68,12 @@ %for cs in c.cs_ranges: ##${comment.comment_inline_form(cs)} ## diff block -

    ${'r%s:%s' % (cs.revision,h.short_id(cs.raw_id))}

    +

    + ${'r%s:%s' % (cs.revision,h.short_id(cs.raw_id))} +
    + gravatar +
    +

    ${diff_block.diff_block(c.changes[cs.raw_id])} ##${comment.comments(cs)} diff --git a/rhodecode/templates/changeset/diff_block.html b/rhodecode/templates/changeset/diff_block.html --- a/rhodecode/templates/changeset/diff_block.html +++ b/rhodecode/templates/changeset/diff_block.html @@ -1,12 +1,12 @@ ## -*- coding: utf-8 -*- ##usage: ## <%namespace name="diff_block" file="/changeset/diff_block.html"/> -## ${diff_block.diff_block(changes)} +## ${diff_block.diff_block(change)} ## -<%def name="diff_block(changes)"> +<%def name="diff_block(change)"> -%for change,filenode,diff,cs1,cs2,stat in changes: - %if change !='removed': +%for op,filenode,diff,cs1,cs2,stat in change: + %if op !='removed':
    @@ -16,9 +16,9 @@ revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
    - - - + + + ${c.ignorews_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))} ${c.context_url(request.GET, h.FID(filenode.changeset.raw_id,filenode.path))}
    @@ -39,3 +39,23 @@ %endfor + +<%def name="diff_block_simple(change)"> + + %for op,filenode_path,diff in change: +
    +
    + +
    +
    + ${diff|n} +
    +
    + %endfor + diff --git a/rhodecode/templates/compare/compare_cs.html b/rhodecode/templates/compare/compare_cs.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/compare/compare_cs.html @@ -0,0 +1,27 @@ +## Changesets table ! +
    +
    gravatar
    gravatar
    ${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}
    ${h.person(cs.author)}
    ${cs.date} + %if c.statuses: +
    + %endif +
    ${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}
    + %if not c.cs_ranges: + + %else: + %for cnt, cs in enumerate(c.cs_ranges): + + + + + + + + + %endfor + %endif +
    ${_('No changesets')}
    gravatar
    + %if cs.raw_id in c.statuses: +
    + %endif +
    ${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.target_repo,revision=cs.raw_id))} + %if c.as_form: + ${h.hidden('revisions',cs.raw_id)} + %endif +
    ${h.person(cs.author)}
    ${cs.date}
    ${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name)}
    +
    diff --git a/rhodecode/templates/compare/compare_diff.html b/rhodecode/templates/compare/compare_diff.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/compare/compare_diff.html @@ -0,0 +1,77 @@ +## -*- coding: utf-8 -*- +<%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)} + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_(u'Home'),h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} + » + ${_('Compare')} + + +<%def name="page_nav()"> + ${self.menu('changelog')} + + +<%def name="main()"> +
    + +
    + ${self.breadcrumbs()} +
    +
    +
    +
    +

    ${_('Compare View')}

    +
    + ${'%s@%s' % (c.org_repo.repo_name, c.org_ref)} -> ${'%s@%s' % (c.other_repo.repo_name, c.other_ref)} [swap] +
    +
    +
    +
    + ##CS +
    ${_('Outgoing changesets')}
    + <%include file="compare_cs.html" /> + + ## FILES +
    ${_('Files affected')}
    +
    + %for fid, change, f, stat in c.files: +
    +
    ${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}
    +
    ${h.fancy_file_stats(stat)}
    +
    + %endfor +
    +
    +
    + + ## diff block + <%namespace name="diff_block" file="/changeset/diff_block.html"/> + %for fid, change, f, stat in c.files: + ${diff_block.diff_block_simple([c.changes[fid]])} + %endfor + + +
    + diff --git a/rhodecode/templates/data_table/_dt_elements.html b/rhodecode/templates/data_table/_dt_elements.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/data_table/_dt_elements.html @@ -0,0 +1,110 @@ +## DATA TABLE RE USABLE ELEMENTS +## 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 name="quick_menu(repo_name)"> + + + +<%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: + return name.split('/')[-1] + else: + return name + %> +
    + ##TYPE OF REPO + %if h.is_hg(rtype): + ${_('Mercurial repository')} + %elif h.is_git(rtype): + ${_('Git repository')} + %endif + + ##PRIVATE/PUBLIC + %if private and c.visual.show_private_icon: + ${_('private repository')} + %elif not private and c.visual.show_public_icon: + ${_('public repository')} + %endif + + ##NAME + %if admin: + ${h.link_to(get_name(name),h.url('edit_repo',repo_name=name),class_="repo_name")} + %else: + ${h.link_to(get_name(name),h.url('summary_home',repo_name=name),class_="repo_name")} + %endif + %if fork_of: + + ${_('fork')} + %endif +
    + + + + +<%def name="revision(name,rev,tip,author,last_msg)"> +
    + %if rev >= 0: +
    ${'r%s:%s' % (rev,h.short_id(tip))}
    + %else: + ${_('No changesets yet')} + %endif +
    + + +<%def name="user_gravatar(email, size=24)"> +
    gravatar
    + + +<%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, + class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")} + ${h.end_form()} + + +<%def name="user_name(user_id, username)"> + ${h.link_to(username,h.url('edit_user', id=user_id))} + diff --git a/rhodecode/templates/email_templates/changeset_comment.html b/rhodecode/templates/email_templates/changeset_comment.html --- a/rhodecode/templates/email_templates/changeset_comment.html +++ b/rhodecode/templates/email_templates/changeset_comment.html @@ -4,3 +4,9 @@

    ${subject}

    ${body} + +% if status_change is not None: +
    + New status -> ${status_change} +
    +% endif diff --git a/rhodecode/templates/errors/error_document.html b/rhodecode/templates/errors/error_document.html --- a/rhodecode/templates/errors/error_document.html +++ b/rhodecode/templates/errors/error_document.html @@ -1,14 +1,16 @@ ## -*- coding: utf-8 -*- - + Error - ${c.error_message} + + + + %if c.redirect_time: %endif - - diff --git a/rhodecode/templates/files/file_diff.html b/rhodecode/templates/files/file_diff.html --- a/rhodecode/templates/files/file_diff.html +++ b/rhodecode/templates/files/file_diff.html @@ -1,11 +1,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('File diff')} - ${c.rhodecode_name} + ${_('%s File diff') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » diff --git a/rhodecode/templates/files/files.html b/rhodecode/templates/files/files.html --- a/rhodecode/templates/files/files.html +++ b/rhodecode/templates/files/files.html @@ -1,11 +1,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Files')} - ${c.rhodecode_name} + ${_('%s files') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('files_home',repo_name=c.repo_name))} » @@ -36,13 +36,107 @@
    + + diff --git a/rhodecode/templates/files/files_add.html b/rhodecode/templates/files/files_add.html --- a/rhodecode/templates/files/files_add.html +++ b/rhodecode/templates/files/files_add.html @@ -1,7 +1,7 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Edit file')} - ${c.rhodecode_name} + ${_('%s Edit file') % c.repo_name} - ${c.rhodecode_name} <%def name="js_extra()"> @@ -12,7 +12,7 @@ <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » diff --git a/rhodecode/templates/files/files_browser.html b/rhodecode/templates/files/files_browser.html --- a/rhodecode/templates/files/files_browser.html +++ b/rhodecode/templates/files/files_browser.html @@ -11,9 +11,9 @@ ${h.form(h.url.current())}
    ${_('view')}@rev - « + « ${h.text('at_rev',value=c.changeset.revision,size=5)} - » + » ## ${h.submit('view',_('view'),class_="ui-btn")}
    ${h.end_form()} @@ -88,14 +88,14 @@ %if node.is_file(): -
    +
    ${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}
    %endif %if node.is_file(): - + ${h.age(node.last_changeset.date)} %endif diff --git a/rhodecode/templates/files/files_edit.html b/rhodecode/templates/files/files_edit.html --- a/rhodecode/templates/files/files_edit.html +++ b/rhodecode/templates/files/files_edit.html @@ -1,7 +1,7 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Edit file')} - ${c.rhodecode_name} + ${_('%s Edit file') % c.repo_name} - ${c.rhodecode_name} <%def name="js_extra()"> @@ -12,7 +12,7 @@ <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » diff --git a/rhodecode/templates/files/files_source.html b/rhodecode/templates/files/files_source.html --- a/rhodecode/templates/files/files_source.html +++ b/rhodecode/templates/files/files_source.html @@ -1,22 +1,34 @@
    -
    ${_('History')}
    +
    ${_('History')}
    -
    - ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')} - ${h.hidden('diff2',c.file.changeset.raw_id)} - ${h.select('diff1',c.file.changeset.raw_id,c.file_history)} - ${h.submit('diff','diff to revision',class_="ui-btn")} - ${h.submit('show_rev','show at revision',class_="ui-btn")} - ${h.end_form()} -
    +
    +
    + ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')} + ${h.hidden('diff2',c.file.changeset.raw_id)} + ${h.select('diff1',c.file.changeset.raw_id,c.file_history)} + ${h.submit('diff',_('diff to revision'),class_="ui-btn")} + ${h.submit('show_rev',_('show at revision'),class_="ui-btn")} + ${h.end_form()} +
    +
    +
    ${h.literal(ungettext(u'%s author',u'%s authors',len(c.authors)) % ('%s' % len(c.authors))) }
    + %for email, user in c.authors: +
    +
    gravatar
    +
    + %endfor +
    +
    +
    +
    -
    ${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}
    +
    ${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}
    ${h.format_byte_size(c.file.size,binary=True)}
    ${c.file.mimetype}
    @@ -36,7 +48,7 @@
    - gravatar + gravatar
    ${h.person(c.file.changeset.author)}
    diff --git a/rhodecode/templates/files/files_ypjax.html b/rhodecode/templates/files/files_ypjax.html --- a/rhodecode/templates/files/files_ypjax.html +++ b/rhodecode/templates/files/files_ypjax.html @@ -9,7 +9,7 @@ <%include file='files_browser.html'/> %else: <%include file='files_source.html'/> - %endif + %endif %else:

    ${_('Go back')} diff --git a/rhodecode/templates/followers/followers.html b/rhodecode/templates/followers/followers.html --- a/rhodecode/templates/followers/followers.html +++ b/rhodecode/templates/followers/followers.html @@ -2,11 +2,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Followers')} - ${c.rhodecode_name} + ${_('%s Followers') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » diff --git a/rhodecode/templates/followers/followers_data.html b/rhodecode/templates/followers/followers_data.html --- a/rhodecode/templates/followers/followers_data.html +++ b/rhodecode/templates/followers/followers_data.html @@ -9,8 +9,8 @@ ${f.user.username} (${f.user.name} ${f.user.lastname})

    -
    ${_('Started following')} - - ${h.age(f.follows_from)}
    +
    ${_('Started following -')} + ${h.age(f.follows_from)}
    % endfor diff --git a/rhodecode/templates/forks/fork.html b/rhodecode/templates/forks/fork.html --- a/rhodecode/templates/forks/fork.html +++ b/rhodecode/templates/forks/fork.html @@ -2,11 +2,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name} + ${_('%s Fork') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))} » @@ -36,12 +36,22 @@ ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
    +
    +
    + +
    +
    + ${h.select('landing_rev','',c.landing_revs,class_="medium")} + ${_('Default revision for files page, downloads, whoosh and readme')} +
    +
    ${h.select('repo_group','',c.repo_groups,class_="medium")} + ${_('Optionaly select a group to put this repository into.')}
    @@ -50,6 +60,7 @@
    ${h.textarea('description',cols=23,rows=5)} + ${_('Keep it short and to the point. Use a README file for longer descriptions.')}
    @@ -58,6 +69,7 @@
    ${h.checkbox('private',value="True")} + ${_('Private repositories are only visible to people explicitly added as collaborators.')}
    @@ -66,6 +78,7 @@
    ${h.checkbox('copy_permissions',value="True", checked="checked")} + ${_('Copy permissions from forked repository')}
    @@ -74,10 +87,11 @@
    ${h.checkbox('update_after_clone',value="True")} + ${_('Checkout source after making a clone')}
    - ${h.submit('',_('fork this repository'),class_="ui-button")} + ${h.submit('',_('fork this repository'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/forks/forks.html b/rhodecode/templates/forks/forks.html --- a/rhodecode/templates/forks/forks.html +++ b/rhodecode/templates/forks/forks.html @@ -2,11 +2,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Forks')} - ${c.rhodecode_name} + ${_('%s Forks') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » diff --git a/rhodecode/templates/forks/forks_data.html b/rhodecode/templates/forks/forks_data.html --- a/rhodecode/templates/forks/forks_data.html +++ b/rhodecode/templates/forks/forks_data.html @@ -15,7 +15,11 @@
    ${_('forked')} - - ${h.age(f.created_on)}
    + ${h.age(f.created_on)} + ${_('Compare fork')} +
    % endfor diff --git a/rhodecode/templates/index_base.html b/rhodecode/templates/index_base.html --- a/rhodecode/templates/index_base.html +++ b/rhodecode/templates/index_base.html @@ -41,7 +41,11 @@ ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))} - ${gr.group_description} + %if c.visual.stylify_metatags: + ${h.desc_stylize(gr.group_description)} + %else: + ${gr.group_description} + %endif ## this is commented out since for multi nested repos can be HEAVY! ## in number of executed queries during traversing uncomment at will ##${gr.repositories_recursive_count} @@ -57,7 +61,7 @@
    <%cnt=0%> - <%namespace name="dt" file="/_data_table/_dt_elements.html"/> + <%namespace name="dt" file="/data_table/_dt_elements.html"/> @@ -85,11 +89,15 @@ ##DESCRIPTION ##LAST CHANGE DATE ##LAST REVISION - @@ -78,6 +78,11 @@

    ${_('Existing repository?')}

    -    ${c.rhodecode_repo.alias} push ${c.clone_repo_url}
    +%if h.is_git(c.rhodecode_repo):
    +    git remote add origin ${c.clone_repo_url}
    +    git push -u origin master
    +%else:
    +    hg push ${c.clone_repo_url}
    +%endif
     
    %endif diff --git a/rhodecode/templates/summary/summary.html b/rhodecode/templates/summary/summary.html --- a/rhodecode/templates/summary/summary.html +++ b/rhodecode/templates/summary/summary.html @@ -1,11 +1,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name} + ${_('%s Summary') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))} » @@ -16,6 +16,11 @@ ${self.menu('summary')} +<%def name="head_extra()"> + + + + <%def name="main()"> <% summary = lambda n:{False:'summary-short'}.get(n) @@ -99,7 +104,11 @@
    -
    ${h.urlify_text(c.dbrepo.description)}
    + %if c.visual.stylify_metatags: +
    ${h.urlify_text(h.desc_stylize(c.dbrepo.description))}
    + %else: +
    ${h.urlify_text(c.dbrepo.description)}
    + %endif
    @@ -158,10 +167,10 @@ %endif %else: ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)} - ${h.link_to('Download as zip',h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")} + ${h.link_to(_('Download as zip'), h.url('files_archive_home',repo_name=c.dbrepo.repo_name,fname='tip.zip'),class_="archive_icon ui-btn")} - + %endif
    @@ -220,11 +229,14 @@ %if c.readme_data: -
    -
    - +
    + -
    +
    ${c.readme_data|n}
    diff --git a/rhodecode/templates/switch_to_list.html b/rhodecode/templates/switch_to_list.html --- a/rhodecode/templates/switch_to_list.html +++ b/rhodecode/templates/switch_to_list.html @@ -4,7 +4,7 @@
      %if c.rhodecode_repo.branches.values(): %for cnt,branch in enumerate(c.rhodecode_repo.branches.items()): -
    • ${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[1]))}
    • +
    • ${h.link_to('%s - %s' % (branch[0],h.short_id(branch[1])),h.url('files_home',repo_name=c.repo_name,revision=branch[0]))}
    • %endfor %else:
    • ${h.link_to(_('There are no branches yet'),'#')}
    • @@ -16,7 +16,7 @@
        %if c.rhodecode_repo.tags.values(): %for cnt,tag in enumerate(c.rhodecode_repo.tags.items()): -
      • ${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[1]))}
      • +
      • ${h.link_to('%s - %s' % (tag[0],h.short_id(tag[1])),h.url('files_home',repo_name=c.repo_name,revision=tag[0]))}
      • %endfor %else:
      • ${h.link_to(_('There are no tags yet'),'#')}
      • diff --git a/rhodecode/templates/tags/tags.html b/rhodecode/templates/tags/tags.html --- a/rhodecode/templates/tags/tags.html +++ b/rhodecode/templates/tags/tags.html @@ -2,13 +2,13 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Tags')} - ${c.rhodecode_name} + ${_('%s Tags') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » diff --git a/rhodecode/templates/tags/tags_data.html b/rhodecode/templates/tags/tags_data.html --- a/rhodecode/templates/tags/tags_data.html +++ b/rhodecode/templates/tags/tags_data.html @@ -18,7 +18,7 @@ -
    + ') + + def test_compare_revisions(self): + self.log_user() + rev1 = '3d8f361e72ab' + rev2 = 'b986218ba1c9' + response = self.app.get(url(controller='compare', action='index', + repo_name=HG_REPO, + org_ref_type="rev", + org_ref=rev1, + other_ref_type="rev", + other_ref=rev2, + )) + response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2)) + ## outgoing changesets between those revisions + response.mustcontain("""r1:%s""" % (HG_REPO, rev1)) + + ## files + response.mustcontain(""".hgignore""" % (HG_REPO, rev1, rev2)) + + def test_compare_remote_repos(self): + self.log_user() + + form_data = dict( + repo_name=HG_FORK, + repo_name_full=HG_FORK, + repo_group=None, + repo_type='hg', + description='', + private=False, + copy_permissions=False, + landing_rev='tip', + update_after_clone=False, + fork_parent_id=Repository.get_by_repo_name(HG_REPO), + ) + RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN) + + Session().commit() + + rev1 = '7d4bc8ec6be5' + rev2 = '56349e29c2af' + + response = self.app.get(url(controller='compare', action='index', + repo_name=HG_REPO, + org_ref_type="rev", + org_ref=rev1, + other_ref_type="rev", + other_ref=rev2, + repo=HG_FORK + )) + + try: + response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2)) + ## outgoing changesets between those revisions + + response.mustcontain("""r6:%s""" % (HG_REPO, rev1)) + response.mustcontain("""r5:6fff84722075""" % (HG_REPO)) + response.mustcontain("""r4:2dda4e345fac""" % (HG_REPO)) + + ## files + response.mustcontain("""vcs/backends/hg.py""" % (HG_REPO, rev1, rev2)) + response.mustcontain("""vcs/backends/__init__.py""" % (HG_REPO, rev1, rev2)) + response.mustcontain("""vcs/backends/base.py""" % (HG_REPO, rev1, rev2)) + finally: + RepoModel().delete(HG_FORK) + + def test_compare_extra_commits(self): + self.log_user() + + repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg', + description='diff-test', + owner=TEST_USER_ADMIN_LOGIN) + + repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg', + description='diff-test', + owner=TEST_USER_ADMIN_LOGIN) + + Session().commit() + r1_id = repo1.repo_id + r1_name = repo1.repo_name + r2_id = repo2.repo_id + r2_name = repo2.repo_name + + #commit something ! + cs0 = ScmModel().create_node( + repo=repo1.scm_instance, repo_name=r1_name, + cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN, + author=TEST_USER_ADMIN_LOGIN, + message='commit1', + content='line1', + f_path='file1' + ) + + cs0_prim = ScmModel().create_node( + repo=repo2.scm_instance, repo_name=r2_name, + cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN, + author=TEST_USER_ADMIN_LOGIN, + message='commit1', + content='line1', + f_path='file1' + ) + + cs1 = ScmModel().commit_change( + repo=repo2.scm_instance, repo_name=r2_name, + cs=cs0_prim, user=TEST_USER_ADMIN_LOGIN, author=TEST_USER_ADMIN_LOGIN, + message='commit2', + content='line1\nline2', + f_path='file1' + ) + + rev1 = 'default' + rev2 = 'default' + response = self.app.get(url(controller='compare', action='index', + repo_name=r2_name, + org_ref_type="branch", + org_ref=rev1, + other_ref_type="branch", + other_ref=rev2, + repo=r1_name + )) + + try: + response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2)) + + response.mustcontain("""
    commit2
    """) + response.mustcontain("""r1:%s""" % (r2_name, cs1.raw_id, cs1.short_id)) + ## files + response.mustcontain("""file1""" % (r2_name, rev1, rev2)) + + + finally: + RepoModel().delete(r1_id) + RepoModel().delete(r2_id) diff --git a/rhodecode/tests/functional/test_files.py b/rhodecode/tests/functional/test_files.py --- a/rhodecode/tests/functional/test_files.py +++ b/rhodecode/tests/functional/test_files.py @@ -186,6 +186,14 @@ class TestFilesController(TestController response.mustcontain("""branch: default""") + def test_file_annotation_git(self): + self.log_user() + response = self.app.get(url(controller='files', action='index', + repo_name=GIT_REPO, + revision='master', + f_path='vcs/nodes.py', + annotate=True)) + def test_archival(self): self.log_user() @@ -200,9 +208,10 @@ class TestFilesController(TestController self.assertEqual(response.status, '200 OK') heads = [ - ('Content-Type', 'text/html; charset=utf-8'), - ('Content-Length', '0'), ('Pragma', 'no-cache'), - ('Cache-Control', 'no-cache') + ('Pragma', 'no-cache'), + ('Cache-Control', 'no-cache'), + ('Content-Disposition', 'attachment; filename=%s' % filename), + ('Content-Type', '%s; charset=utf-8' % info[0]), ] self.assertEqual(response.response._headers.items(), heads) @@ -212,7 +221,8 @@ class TestFilesController(TestController for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']: fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext - response = self.app.get(url(controller='files', action='archivefile', + response = self.app.get(url(controller='files', + action='archivefile', repo_name=HG_REPO, fname=fname)) response.mustcontain('Unknown archive type') @@ -220,10 +230,11 @@ class TestFilesController(TestController def test_archival_wrong_revision(self): self.log_user() - for rev in ['00x000000', 'tar', 'wrong', '@##$@$424213232', '232dffcd']: + for rev in ['00x000000', 'tar', 'wrong', '@##$@$42413232', '232dffcd']: fname = '%s.zip' % rev - response = self.app.get(url(controller='files', action='archivefile', + response = self.app.get(url(controller='files', + action='archivefile', repo_name=HG_REPO, fname=fname)) response.mustcontain('Unknown revision') diff --git a/rhodecode/tests/functional/test_forks.py b/rhodecode/tests/functional/test_forks.py --- a/rhodecode/tests/functional/test_forks.py +++ b/rhodecode/tests/functional/test_forks.py @@ -3,6 +3,7 @@ from rhodecode.tests import * from rhodecode.model.db import Repository from rhodecode.model.repo import RepoModel from rhodecode.model.user import UserModel +from rhodecode.model.meta import Session class TestForksController(TestController): @@ -12,13 +13,13 @@ class TestForksController(TestController self.password = u'qweqwe' self.u1 = UserModel().create_or_update( username=self.username, password=self.password, - email=u'fork_king@rhodecode.org', name=u'u1', lastname=u'u1' + email=u'fork_king@rhodecode.org', firstname=u'u1', lastname=u'u1' ) - self.Session.commit() + Session().commit() def tearDown(self): - self.Session.delete(self.u1) - self.Session.commit() + Session().delete(self.u1) + Session().commit() def test_index(self): self.log_user() @@ -28,7 +29,21 @@ class TestForksController(TestController self.assertTrue("""There are no forks yet""" in response.body) - def test_index_with_fork(self): + def test_no_permissions_to_fork(self): + usr = self.log_user(TEST_USER_REGULAR_LOGIN, + TEST_USER_REGULAR_PASS)['user_id'] + user_model = UserModel() + user_model.revoke_perm(usr, 'hg.fork.repository') + user_model.grant_perm(usr, 'hg.fork.none') + u = UserModel().get(usr) + u.inherit_default_permissions = False + Session().commit() + # try create a fork + repo_name = HG_REPO + self.app.post(url(controller='forks', action='fork_create', + repo_name=repo_name), {}, status=403) + + def test_index_with_fork_hg(self): self.log_user() # create a fork @@ -39,19 +54,49 @@ class TestForksController(TestController response = self.app.post(url(controller='forks', action='fork_create', repo_name=repo_name), - {'repo_name':fork_name, - 'repo_group':'', - 'fork_parent_id':org_repo.repo_id, - 'repo_type':'hg', - 'description':description, - 'private':'False'}) + {'repo_name': fork_name, + 'repo_group': '', + 'fork_parent_id': org_repo.repo_id, + 'repo_type': 'hg', + 'description': description, + 'private': 'False', + 'landing_rev': 'tip'}) response = self.app.get(url(controller='forks', action='forks', repo_name=repo_name)) - self.assertTrue("""""" - """vcs_test_hg_fork""" % fork_name - in response.body) + response.mustcontain( + """%s""" % (fork_name, fork_name) + ) + + #remove this fork + response = self.app.delete(url('repo', repo_name=fork_name)) + + def test_index_with_fork_git(self): + self.log_user() + + # create a fork + fork_name = GIT_FORK + description = 'fork of vcs test' + repo_name = GIT_REPO + org_repo = Repository.get_by_repo_name(repo_name) + response = self.app.post(url(controller='forks', + action='fork_create', + repo_name=repo_name), + {'repo_name': fork_name, + 'repo_group': '', + 'fork_parent_id': org_repo.repo_id, + 'repo_type': 'git', + 'description': description, + 'private': 'False', + 'landing_rev': 'tip'}) + + response = self.app.get(url(controller='forks', action='forks', + repo_name=repo_name)) + + response.mustcontain( + """%s""" % (fork_name, fork_name) + ) #remove this fork response = self.app.delete(url('repo', repo_name=fork_name)) @@ -69,14 +114,15 @@ class TestForksController(TestController 'fork_parent_id':org_repo.repo_id, 'repo_type':'hg', 'description':description, - 'private':'False'}) + 'private':'False', + 'landing_rev': 'tip'}) #test if we have a message that fork is ok - self.assertTrue('forked %s repository as %s' \ - % (repo_name, fork_name) in response.session['flash'][0]) + self.checkSessionFlash(response, + 'forked %s repository as %s' % (repo_name, fork_name)) #test if the fork was created in the database - fork_repo = self.Session.query(Repository)\ + fork_repo = Session().query(Repository)\ .filter(Repository.repo_name == fork_name).one() self.assertEqual(fork_repo.repo_name, fork_name) @@ -85,10 +131,6 @@ class TestForksController(TestController #test if fork is visible in the list ? response = response.follow() - # check if fork is marked as fork - # wait for cache to expire - import time - time.sleep(10) response = self.app.get(url(controller='summary', action='index', repo_name=fork_name)) @@ -98,7 +140,7 @@ class TestForksController(TestController usr = self.log_user(self.username, self.password)['user_id'] repo_name = HG_REPO - forks = self.Session.query(Repository)\ + forks = Session().query(Repository)\ .filter(Repository.fork_id != None)\ .all() self.assertEqual(1, len(forks)) @@ -107,7 +149,7 @@ class TestForksController(TestController RepoModel().grant_user_permission(repo=forks[0], user=usr, perm='repository.read') - self.Session.commit() + Session().commit() response = self.app.get(url(controller='forks', action='forks', repo_name=repo_name)) @@ -118,7 +160,7 @@ class TestForksController(TestController usr = self.log_user(self.username, self.password)['user_id'] repo_name = HG_REPO - forks = self.Session.query(Repository)\ + forks = Session().query(Repository)\ .filter(Repository.fork_id != None)\ .all() self.assertEqual(1, len(forks)) @@ -126,7 +168,7 @@ class TestForksController(TestController # set none RepoModel().grant_user_permission(repo=forks[0], user=usr, perm='repository.none') - self.Session.commit() + Session().commit() # fork shouldn't be there response = self.app.get(url(controller='forks', action='forks', repo_name=repo_name)) diff --git a/rhodecode/tests/functional/test_home.py b/rhodecode/tests/functional/test_home.py --- a/rhodecode/tests/functional/test_home.py +++ b/rhodecode/tests/functional/test_home.py @@ -1,4 +1,7 @@ +import time from rhodecode.tests import * +from rhodecode.model.meta import Session +from rhodecode.model.db import User class TestHomeController(TestController): @@ -18,5 +21,41 @@ class TestHomeController(TestController) """open.png"/>""") response.mustcontain( -"""r173:27cd5cce30c9""") +"""r173:27cd5cce30c9""" +) + + def test_repo_summary_with_anonymous_access_disabled(self): + anon = User.get_by_username('default') + anon.active = False + Session().add(anon) + Session().commit() + time.sleep(1.5) # must sleep for cache (1s to expire) + try: + response = self.app.get(url(controller='summary', + action='index', repo_name=HG_REPO), + status=302) + assert 'login' in response.location + + finally: + anon = User.get_by_username('default') + anon.active = True + Session().add(anon) + Session().commit() + + def test_index_with_anonymous_access_disabled(self): + anon = User.get_by_username('default') + anon.active = False + Session().add(anon) + Session().commit() + time.sleep(1.5) # must sleep for cache (1s to expire) + try: + response = self.app.get(url(controller='home', action='index'), + status=302) + assert 'login' in response.location + finally: + anon = User.get_by_username('default') + anon.active = True + Session().add(anon) + Session().commit() diff --git a/rhodecode/tests/functional/test_login.py b/rhodecode/tests/functional/test_login.py --- a/rhodecode/tests/functional/test_login.py +++ b/rhodecode/tests/functional/test_login.py @@ -3,16 +3,17 @@ from rhodecode.tests import * from rhodecode.model.db import User, Notification from rhodecode.lib.utils2 import generate_api_key from rhodecode.lib.auth import check_password -from rhodecode.model.meta import Session +from rhodecode.lib import helpers as h +from rhodecode.model import validators class TestLoginController(TestController): def tearDown(self): for n in Notification.query().all(): - Session.delete(n) + self.Session().delete(n) - Session.commit() + self.Session().commit() self.assertEqual(Notification.query().all(), []) def test_index(self): @@ -22,21 +23,21 @@ class TestLoginController(TestController def test_login_admin_ok(self): response = self.app.post(url(controller='login', action='index'), - {'username':'test_admin', - 'password':'test12'}) + {'username': 'test_admin', + 'password': 'test12'}) self.assertEqual(response.status, '302 Found') - self.assertEqual(response.session['rhodecode_user'].get('username') , + self.assertEqual(response.session['rhodecode_user'].get('username'), 'test_admin') response = response.follow() self.assertTrue('%s repository' % HG_REPO in response.body) def test_login_regular_ok(self): response = self.app.post(url(controller='login', action='index'), - {'username':'test_regular', - 'password':'test12'}) + {'username': 'test_regular', + 'password': 'test12'}) self.assertEqual(response.status, '302 Found') - self.assertEqual(response.session['rhodecode_user'].get('username') , + self.assertEqual(response.session['rhodecode_user'].get('username'), 'test_regular') response = response.follow() self.assertTrue('%s repository' % HG_REPO in response.body) @@ -46,27 +47,45 @@ class TestLoginController(TestController test_came_from = '/_admin/users' response = self.app.post(url(controller='login', action='index', came_from=test_came_from), - {'username':'test_admin', - 'password':'test12'}) + {'username': 'test_admin', + 'password': 'test12'}) self.assertEqual(response.status, '302 Found') response = response.follow() self.assertEqual(response.status, '200 OK') self.assertTrue('Users administration' in response.body) + @parameterized.expand([ + ('data:text/html,',), + ('mailto:test@rhodecode.org',), + ('file:///etc/passwd',), + ('ftp://some.ftp.server',), + ('http://other.domain',), + ]) + def test_login_bad_came_froms(self, url_came_from): + response = self.app.post(url(controller='login', action='index', + came_from=url_came_from), + {'username': 'test_admin', + 'password': 'test12'}) + self.assertEqual(response.status, '302 Found') + self.assertEqual(response._environ['paste.testing_variables'] + ['tmpl_context'].came_from, '/') + response = response.follow() + + self.assertEqual(response.status, '200 OK') + def test_login_short_password(self): response = self.app.post(url(controller='login', action='index'), - {'username':'test_admin', - 'password':'as'}) + {'username': 'test_admin', + 'password': 'as'}) self.assertEqual(response.status, '200 OK') self.assertTrue('Enter 3 characters or more' in response.body) def test_login_wrong_username_password(self): response = self.app.post(url(controller='login', action='index'), - {'username':'error', - 'password':'test12'}) - self.assertEqual(response.status , '200 OK') + {'username': 'error', + 'password': 'test12'}) self.assertTrue('invalid user name' in response.body) self.assertTrue('invalid password' in response.body) @@ -79,62 +98,63 @@ class TestLoginController(TestController self.assertTrue('Sign Up to RhodeCode' in response.body) def test_register_err_same_username(self): + uname = 'test_admin' response = self.app.post(url(controller='login', action='register'), - {'username':'test_admin', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'goodmail@domain.com', - 'name':'test', - 'lastname':'test'}) + {'username': uname, + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'goodmail@domain.com', + 'firstname': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - self.assertTrue('This username already exists' in response.body) + msg = validators.ValidUsername()._messages['username_exists'] + msg = h.html_escape(msg % {'username': uname}) + response.mustcontain(msg) def test_register_err_same_email(self): response = self.app.post(url(controller='login', action='register'), - {'username':'test_admin_0', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'test_admin@mail.com', - 'name':'test', - 'lastname':'test'}) + {'username': 'test_admin_0', + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'test_admin@mail.com', + 'firstname': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - response.mustcontain('This e-mail address is already taken') + msg = validators.UniqSystemEmail()()._messages['email_taken'] + response.mustcontain(msg) def test_register_err_same_email_case_sensitive(self): response = self.app.post(url(controller='login', action='register'), - {'username':'test_admin_1', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'TesT_Admin@mail.COM', - 'name':'test', - 'lastname':'test'}) - self.assertEqual(response.status , '200 OK') - response.mustcontain('This e-mail address is already taken') + {'username': 'test_admin_1', + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'TesT_Admin@mail.COM', + 'firstname': 'test', + 'lastname': 'test'}) + msg = validators.UniqSystemEmail()()._messages['email_taken'] + response.mustcontain(msg) def test_register_err_wrong_data(self): response = self.app.post(url(controller='login', action='register'), - {'username':'xs', - 'password':'test', - 'password_confirmation':'test', - 'email':'goodmailm', - 'name':'test', - 'lastname':'test'}) - self.assertEqual(response.status , '200 OK') + {'username': 'xs', + 'password': 'test', + 'password_confirmation': 'test', + 'email': 'goodmailm', + 'firstname': 'test', + 'lastname': 'test'}) + self.assertEqual(response.status, '200 OK') response.mustcontain('An email address must contain a single @') response.mustcontain('Enter a value 6 characters long or more') def test_register_err_username(self): response = self.app.post(url(controller='login', action='register'), - {'username':'error user', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'goodmailm', - 'name':'test', - 'lastname':'test'}) + {'username': 'error user', + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'goodmailm', + 'firstname': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') response.mustcontain('An email address must contain a single @') response.mustcontain('Username may only contain ' 'alphanumeric characters underscores, ' @@ -142,41 +162,42 @@ class TestLoginController(TestController 'alphanumeric character') def test_register_err_case_sensitive(self): + usr = 'Test_Admin' response = self.app.post(url(controller='login', action='register'), - {'username':'Test_Admin', - 'password':'test12', - 'password_confirmation':'test12', - 'email':'goodmailm', - 'name':'test', - 'lastname':'test'}) + {'username': usr, + 'password': 'test12', + 'password_confirmation': 'test12', + 'email': 'goodmailm', + 'firstname': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - self.assertTrue('An email address must contain a single @' in response.body) - self.assertTrue('This username already exists' in response.body) + response.mustcontain('An email address must contain a single @') + msg = validators.ValidUsername()._messages['username_exists'] + msg = h.html_escape(msg % {'username': usr}) + response.mustcontain(msg) def test_register_special_chars(self): response = self.app.post(url(controller='login', action='register'), - {'username':'xxxaxn', - 'password':'ąćźżąśśśś', - 'password_confirmation':'ąćźżąśśśś', - 'email':'goodmailm@test.plx', - 'name':'test', - 'lastname':'test'}) + {'username': 'xxxaxn', + 'password': 'ąćźżąśśśś', + 'password_confirmation': 'ąćźżąśśśś', + 'email': 'goodmailm@test.plx', + 'firstname': 'test', + 'lastname': 'test'}) - self.assertEqual(response.status , '200 OK') - self.assertTrue('Invalid characters in password' in response.body) + msg = validators.ValidPassword()._messages['invalid_password'] + response.mustcontain(msg) def test_register_password_mismatch(self): response = self.app.post(url(controller='login', action='register'), - {'username':'xs', - 'password':'123qwe', - 'password_confirmation':'qwe123', - 'email':'goodmailm@test.plxa', - 'name':'test', - 'lastname':'test'}) - - self.assertEqual(response.status, '200 OK') - response.mustcontain('Passwords do not match') + {'username': 'xs', + 'password': '123qwe', + 'password_confirmation': 'qwe123', + 'email': 'goodmailm@test.plxa', + 'firstname': 'test', + 'lastname': 'test'}) + msg = validators.ValidPasswordsMatch()._messages['password_mismatch'] + response.mustcontain(msg) def test_register_ok(self): username = 'test_regular4' @@ -186,17 +207,17 @@ class TestLoginController(TestController lastname = 'testlastname' response = self.app.post(url(controller='login', action='register'), - {'username':username, - 'password':password, - 'password_confirmation':password, - 'email':email, - 'name':name, - 'lastname':lastname, - 'admin':True}) # This should be overriden + {'username': username, + 'password': password, + 'password_confirmation': password, + 'email': email, + 'firstname': name, + 'lastname': lastname, + 'admin': True}) # This should be overriden self.assertEqual(response.status, '302 Found') self.checkSessionFlash(response, 'You have successfully registered into rhodecode') - ret = self.Session.query(User).filter(User.username == 'test_regular4').one() + ret = self.Session().query(User).filter(User.username == 'test_regular4').one() self.assertEqual(ret.username, username) self.assertEqual(check_password(password, ret.password), True) self.assertEqual(ret.email, email) @@ -206,12 +227,15 @@ class TestLoginController(TestController self.assertEqual(ret.admin, False) def test_forgot_password_wrong_mail(self): + bad_email = 'marcin@wrongmail.org' response = self.app.post( url(controller='login', action='password_reset'), - {'email': 'marcin@wrongmail.org',} + {'email': bad_email, } ) - response.mustcontain("This e-mail address doesn't exist") + msg = validators.ValidSystemEmail()._messages['non_existing_email'] + msg = h.html_escape(msg % {'email': bad_email}) + response.mustcontain() def test_forgot_password(self): response = self.app.get(url(controller='login', @@ -231,12 +255,12 @@ class TestLoginController(TestController new.name = name new.lastname = lastname new.api_key = generate_api_key(username) - self.Session.add(new) - self.Session.commit() + self.Session().add(new) + self.Session().commit() response = self.app.post(url(controller='login', action='password_reset'), - {'email':email, }) + {'email': email, }) self.checkSessionFlash(response, 'Your password reset link was sent') diff --git a/rhodecode/tests/functional/test_pullrequests.py b/rhodecode/tests/functional/test_pullrequests.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/functional/test_pullrequests.py @@ -0,0 +1,9 @@ +from rhodecode.tests import * + + +class TestPullrequestsController(TestController): + + def test_index(self): + self.log_user() + response = self.app.get(url(controller='pullrequests', action='index', + repo_name=HG_REPO)) diff --git a/rhodecode/tests/functional/test_search.py b/rhodecode/tests/functional/test_search.py --- a/rhodecode/tests/functional/test_search.py +++ b/rhodecode/tests/functional/test_search.py @@ -1,7 +1,8 @@ +import os from rhodecode.tests import * -import os from nose.plugins.skip import SkipTest + class TestSearchController(TestController): def test_index(self): @@ -18,20 +19,96 @@ class TestSearchController(TestControlle else: self.log_user() response = self.app.get(url(controller='search', action='index'), - {'q':HG_REPO}) + {'q': HG_REPO}) self.assertTrue('There is no index to search in. ' 'Please run whoosh indexer' in response.body) def test_normal_search(self): self.log_user() response = self.app.get(url(controller='search', action='index'), - {'q':'def repo'}) - self.assertTrue('10 results' in response.body) - self.assertTrue('Permission denied' not in response.body) + {'q': 'def repo'}) + response.mustcontain('39 results') def test_repo_search(self): self.log_user() response = self.app.get(url(controller='search', action='index'), - {'q':'repository:%s def test' % HG_REPO}) - self.assertTrue('4 results' in response.body) - self.assertTrue('Permission denied' not in response.body) + {'q': 'repository:%s def test' % HG_REPO}) + + response.mustcontain('4 results') + + def test_search_last(self): + self.log_user() + response = self.app.get(url(controller='search', action='index'), + {'q': 'last:t', 'type': 'commit'}) + + response.mustcontain('2 results') + + def test_search_commit_message(self): + self.log_user() + response = self.app.get(url(controller='search', action='index'), + {'q': 'bother to ask where to fetch repo during tests', + 'type': 'commit'}) + + response.mustcontain('2 results') + response.mustcontain('a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1') + response.mustcontain('c6eb379775c578a95dad8ddab53f963b80894850') + + def test_search_commit_message_hg_repo(self): + self.log_user() + response = self.app.get(url(controller='search', action='index', + search_repo=HG_REPO), + {'q': 'bother to ask where to fetch repo during tests', + 'type': 'commit'}) + + response.mustcontain('1 results') + response.mustcontain('a00c1b6f5d7a6ae678fd553a8b81d92367f7ecf1') + + def test_search_commit_changed_file(self): + self.log_user() + response = self.app.get(url(controller='search', action='index'), + {'q': 'changed:tests/utils.py', + 'type': 'commit'}) + + response.mustcontain('20 results') + + def test_search_commit_changed_files_get_commit(self): + self.log_user() + response = self.app.get(url(controller='search', action='index'), + {'q': 'changed:vcs/utils/lazy.py', + 'type': 'commit'}) + + response.mustcontain('7 results') + response.mustcontain('36e0fc9d2808c5022a24f49d6658330383ed8666') + response.mustcontain('af182745859d779f17336241a0815d15166ae1ee') + response.mustcontain('17438a11f72b93f56d0e08e7d1fa79a378578a82') + response.mustcontain('33fa3223355104431402a888fa77a4e9956feb3e') + response.mustcontain('d1f898326327e20524fe22417c22d71064fe54a1') + response.mustcontain('fe568b4081755c12abf6ba673ba777fc02a415f3') + response.mustcontain('bafe786f0d8c2ff7da5c1dcfcfa577de0b5e92f1') + + def test_search_commit_added_file(self): + self.log_user() + response = self.app.get(url(controller='search', action='index'), + {'q': 'added:README.rst', + 'type': 'commit'}) + + response.mustcontain('2 results') + #HG + response.mustcontain('3803844fdbd3b711175fc3da9bdacfcd6d29a6fb') + #GIT + response.mustcontain('ff7ca51e58c505fec0dd2491de52c622bb7a806b') + + def test_search_author(self): + self.log_user() + response = self.app.get(url(controller='search', action='index'), + {'q': 'author:marcin@python-blog.com raw_id:b986218ba1c9b0d6a259fac9b050b1724ed8e545', + 'type': 'commit'}) + + response.mustcontain('1 results') + + def test_search_file_name(self): + self.log_user() + response = self.app.get(url(controller='search', action='index'), + {'q': 'README.rst', 'type': 'path'}) + + response.mustcontain('2 results') diff --git a/rhodecode/tests/functional/test_summary.py b/rhodecode/tests/functional/test_summary.py --- a/rhodecode/tests/functional/test_summary.py +++ b/rhodecode/tests/functional/test_summary.py @@ -15,8 +15,8 @@ class TestSummaryController(TestControll #repo type response.mustcontain( """Mercurial """ + """title="Mercurial repository" alt="Mercurial repository" """ + """src="/images/icons/hgicon.png"/>""" ) response.mustcontain( """""") - response.mustcontain("""""") + response.mustcontain("""""" % HG_REPO) + response.mustcontain("""""" % ID) + + def test_index_git(self): + self.log_user() + ID = Repository.get_by_repo_name(GIT_REPO).repo_id + response = self.app.get(url(controller='summary', + action='index', + repo_name=GIT_REPO)) - def test_index_by_id(self): + #repo type + response.mustcontain( + """Git repository""" + ) + response.mustcontain( + """public """ + ) + + # clone url... + response.mustcontain("""""" % GIT_REPO) + response.mustcontain("""""" % ID) + + def test_index_by_id_hg(self): self.log_user() ID = Repository.get_by_repo_name(HG_REPO).repo_id response = self.app.get(url(controller='summary', @@ -59,6 +82,21 @@ class TestSummaryController(TestControll """title="public repository" alt="public """ """repository" src="/images/icons/lock_open.png"/>""") + def test_index_by_id_git(self): + self.log_user() + ID = Repository.get_by_repo_name(GIT_REPO).repo_id + response = self.app.get(url(controller='summary', + action='index', + repo_name='_%s' % ID)) + + #repo type + response.mustcontain("""Git """) + response.mustcontain("""public """) + def _enable_stats(self): r = Repository.get_by_repo_name(HG_REPO) r.enable_statistics = True diff --git a/rhodecode/tests/models/__init__.py b/rhodecode/tests/models/__init__.py new file mode 100644 diff --git a/rhodecode/tests/test_models.py b/rhodecode/tests/models/test_notifications.py rename from rhodecode/tests/test_models.py rename to rhodecode/tests/models/test_notifications.py --- a/rhodecode/tests/test_models.py +++ b/rhodecode/tests/models/test_notifications.py @@ -2,204 +2,11 @@ import os import unittest from rhodecode.tests import * -from rhodecode.model.repos_group import ReposGroupModel -from rhodecode.model.repo import RepoModel -from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \ - UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm,\ - Repository -from sqlalchemy.exc import IntegrityError +from rhodecode.model.db import User, Notification, UserNotification from rhodecode.model.user import UserModel from rhodecode.model.meta import Session from rhodecode.model.notification import NotificationModel -from rhodecode.model.users_group import UsersGroupModel -from rhodecode.lib.auth import AuthUser - - -def _make_group(path, desc='desc', parent_id=None, - skip_if_exists=False): - - gr = RepoGroup.get_by_group_name(path) - if gr and skip_if_exists: - return gr - - gr = ReposGroupModel().create(path, desc, parent_id) - return gr - - -class TestReposGroups(unittest.TestCase): - - def setUp(self): - self.g1 = _make_group('test1', skip_if_exists=True) - Session.commit() - self.g2 = _make_group('test2', skip_if_exists=True) - Session.commit() - self.g3 = _make_group('test3', skip_if_exists=True) - Session.commit() - - def tearDown(self): - print 'out' - - def __check_path(self, *path): - """ - Checks the path for existance ! - """ - path = [TESTS_TMP_PATH] + list(path) - path = os.path.join(*path) - return os.path.isdir(path) - - def _check_folders(self): - print os.listdir(TESTS_TMP_PATH) - - def __delete_group(self, id_): - ReposGroupModel().delete(id_) - - def __update_group(self, id_, path, desc='desc', parent_id=None): - form_data = dict( - group_name=path, - group_description=desc, - group_parent_id=parent_id, - perms_updates=[], - perms_new=[] - ) - gr = ReposGroupModel().update(id_, form_data) - return gr - - def test_create_group(self): - g = _make_group('newGroup') - self.assertEqual(g.full_path, 'newGroup') - - self.assertTrue(self.__check_path('newGroup')) - - def test_create_same_name_group(self): - self.assertRaises(IntegrityError, lambda:_make_group('newGroup')) - Session.rollback() - - def test_same_subgroup(self): - sg1 = _make_group('sub1', parent_id=self.g1.group_id) - self.assertEqual(sg1.parent_group, self.g1) - self.assertEqual(sg1.full_path, 'test1/sub1') - self.assertTrue(self.__check_path('test1', 'sub1')) - - ssg1 = _make_group('subsub1', parent_id=sg1.group_id) - self.assertEqual(ssg1.parent_group, sg1) - self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1') - self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1')) - - def test_remove_group(self): - sg1 = _make_group('deleteme') - self.__delete_group(sg1.group_id) - - self.assertEqual(RepoGroup.get(sg1.group_id), None) - self.assertFalse(self.__check_path('deteteme')) - - sg1 = _make_group('deleteme', parent_id=self.g1.group_id) - self.__delete_group(sg1.group_id) - - self.assertEqual(RepoGroup.get(sg1.group_id), None) - self.assertFalse(self.__check_path('test1', 'deteteme')) - - def test_rename_single_group(self): - sg1 = _make_group('initial') - - new_sg1 = self.__update_group(sg1.group_id, 'after') - self.assertTrue(self.__check_path('after')) - self.assertEqual(RepoGroup.get_by_group_name('initial'), None) - - def test_update_group_parent(self): - - sg1 = _make_group('initial', parent_id=self.g1.group_id) - - new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id) - self.assertTrue(self.__check_path('test1', 'after')) - self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None) - - new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id) - self.assertTrue(self.__check_path('test3', 'after')) - self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None) - - new_sg1 = self.__update_group(sg1.group_id, 'hello') - self.assertTrue(self.__check_path('hello')) - - self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1) - - def test_subgrouping_with_repo(self): - - g1 = _make_group('g1') - g2 = _make_group('g2') - - # create new repo - form_data = dict(repo_name='john', - repo_name_full='john', - fork_name=None, - description=None, - repo_group=None, - private=False, - repo_type='hg', - clone_uri=None) - cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN) - r = RepoModel().create(form_data, cur_user) - - self.assertEqual(r.repo_name, 'john') - - # put repo into group - form_data = form_data - form_data['repo_group'] = g1.group_id - form_data['perms_new'] = [] - form_data['perms_updates'] = [] - RepoModel().update(r.repo_name, form_data) - self.assertEqual(r.repo_name, 'g1/john') - - self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id) - self.assertTrue(self.__check_path('g2', 'g1')) - - # test repo - self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1', r.just_name])) - - def test_move_to_root(self): - g1 = _make_group('t11') - Session.commit() - g2 = _make_group('t22', parent_id=g1.group_id) - Session.commit() - - self.assertEqual(g2.full_path, 't11/t22') - self.assertTrue(self.__check_path('t11', 't22')) - - g2 = self.__update_group(g2.group_id, 'g22', parent_id=None) - Session.commit() - - self.assertEqual(g2.group_name, 'g22') - # we moved out group from t1 to '' so it's full path should be 'g2' - self.assertEqual(g2.full_path, 'g22') - self.assertFalse(self.__check_path('t11', 't22')) - self.assertTrue(self.__check_path('g22')) - - -class TestUser(unittest.TestCase): - def __init__(self, methodName='runTest'): - Session.remove() - super(TestUser, self).__init__(methodName=methodName) - - def test_create_and_remove(self): - usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe', - email=u'u232@rhodecode.org', - name=u'u1', lastname=u'u1') - Session.commit() - self.assertEqual(User.get_by_username(u'test_user'), usr) - - # make users group - users_group = UsersGroupModel().create('some_example_group') - Session.commit() - - UsersGroupModel().add_user_to_group(users_group, usr) - Session.commit() - - self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group) - self.assertEqual(UsersGroupMember.query().count(), 1) - UserModel().delete(usr.user_id) - Session.commit() - - self.assertEqual(UsersGroupMember.query().all(), []) class TestNotifications(unittest.TestCase): @@ -209,31 +16,31 @@ class TestNotifications(unittest.TestCas self.u1 = UserModel().create_or_update(username=u'u1', password=u'qweqwe', email=u'u1@rhodecode.org', - name=u'u1', lastname=u'u1') - Session.commit() + firstname=u'u1', lastname=u'u1') + Session().commit() self.u1 = self.u1.user_id self.u2 = UserModel().create_or_update(username=u'u2', password=u'qweqwe', email=u'u2@rhodecode.org', - name=u'u2', lastname=u'u3') - Session.commit() + firstname=u'u2', lastname=u'u3') + Session().commit() self.u2 = self.u2.user_id self.u3 = UserModel().create_or_update(username=u'u3', password=u'qweqwe', email=u'u3@rhodecode.org', - name=u'u3', lastname=u'u3') - Session.commit() + firstname=u'u3', lastname=u'u3') + Session().commit() self.u3 = self.u3.user_id super(TestNotifications, self).__init__(methodName=methodName) def _clean_notifications(self): for n in Notification.query().all(): - Session.delete(n) + Session().delete(n) - Session.commit() + Session().commit() self.assertEqual(Notification.query().all(), []) def tearDown(self): @@ -247,21 +54,23 @@ class TestNotifications(unittest.TestCas notification = NotificationModel().create(created_by=self.u1, subject=u'subj', body=u'hi there', recipients=usrs) - Session.commit() + Session().commit() u1 = User.get(self.u1) u2 = User.get(self.u2) u3 = User.get(self.u3) notifications = Notification.query().all() self.assertEqual(len(notifications), 1) + self.assertEqual(notifications[0].recipients, [u1, u2]) + self.assertEqual(notification.notification_id, + notifications[0].notification_id) + unotification = UserNotification.query()\ .filter(UserNotification.notification == notification).all() - self.assertEqual(notifications[0].recipients, [u1, u2]) - self.assertEqual(notification.notification_id, - notifications[0].notification_id) self.assertEqual(len(unotification), len(usrs)) - self.assertEqual([x.user.user_id for x in unotification], usrs) + self.assertEqual(set([x.user.user_id for x in unotification]), + set(usrs)) def test_user_notifications(self): self.assertEqual([], Notification.query().all()) @@ -270,12 +79,12 @@ class TestNotifications(unittest.TestCas notification1 = NotificationModel().create(created_by=self.u1, subject=u'subj', body=u'hi there1', recipients=[self.u3]) - Session.commit() + Session().commit() notification2 = NotificationModel().create(created_by=self.u1, subject=u'subj', body=u'hi there2', recipients=[self.u3]) - Session.commit() - u3 = Session.query(User).get(self.u3) + Session().commit() + u3 = Session().query(User).get(self.u3) self.assertEqual(sorted([x.notification for x in u3.notifications]), sorted([notification2, notification1])) @@ -287,12 +96,12 @@ class TestNotifications(unittest.TestCas notification = NotificationModel().create(created_by=self.u1, subject=u'title', body=u'hi there3', recipients=[self.u3, self.u1, self.u2]) - Session.commit() + Session().commit() notifications = Notification.query().all() self.assertTrue(notification in notifications) Notification.delete(notification.notification_id) - Session.commit() + Session().commit() notifications = Notification.query().all() self.assertFalse(notification in notifications) @@ -309,7 +118,7 @@ class TestNotifications(unittest.TestCas notification = NotificationModel().create(created_by=self.u1, subject=u'title', body=u'hi there3', recipients=[self.u3, self.u1, self.u2]) - Session.commit() + Session().commit() unotification = UserNotification.query()\ .filter(UserNotification.notification == @@ -321,7 +130,7 @@ class TestNotifications(unittest.TestCas NotificationModel().delete(self.u3, notification.notification_id) - Session.commit() + Session().commit() u3notification = UserNotification.query()\ .filter(UserNotification.notification == @@ -356,7 +165,7 @@ class TestNotifications(unittest.TestCas NotificationModel().create(created_by=self.u1, subject=u'title', body=u'hi there_delete', recipients=[self.u3, self.u1]) - Session.commit() + Session().commit() self.assertEqual(NotificationModel() .get_unread_cnt_for_user(self.u1), 1) @@ -368,7 +177,7 @@ class TestNotifications(unittest.TestCas notification = NotificationModel().create(created_by=self.u1, subject=u'title', body=u'hi there3', recipients=[self.u3, self.u1, self.u2]) - Session.commit() + Session().commit() self.assertEqual(NotificationModel() .get_unread_cnt_for_user(self.u1), 2) @@ -376,340 +185,3 @@ class TestNotifications(unittest.TestCas .get_unread_cnt_for_user(self.u2), 1) self.assertEqual(NotificationModel() .get_unread_cnt_for_user(self.u3), 2) - - -class TestUsers(unittest.TestCase): - - def __init__(self, methodName='runTest'): - super(TestUsers, self).__init__(methodName=methodName) - - def setUp(self): - self.u1 = UserModel().create_or_update(username=u'u1', - password=u'qweqwe', - email=u'u1@rhodecode.org', - name=u'u1', lastname=u'u1') - - def tearDown(self): - perm = Permission.query().all() - for p in perm: - UserModel().revoke_perm(self.u1, p) - - UserModel().delete(self.u1) - Session.commit() - - def test_add_perm(self): - perm = Permission.query().all()[0] - UserModel().grant_perm(self.u1, perm) - Session.commit() - self.assertEqual(UserModel().has_perm(self.u1, perm), True) - - def test_has_perm(self): - perm = Permission.query().all() - for p in perm: - has_p = UserModel().has_perm(self.u1, p) - self.assertEqual(False, has_p) - - def test_revoke_perm(self): - perm = Permission.query().all()[0] - UserModel().grant_perm(self.u1, perm) - Session.commit() - self.assertEqual(UserModel().has_perm(self.u1, perm), True) - - #revoke - UserModel().revoke_perm(self.u1, perm) - Session.commit() - self.assertEqual(UserModel().has_perm(self.u1, perm), False) - - -class TestPermissions(unittest.TestCase): - def __init__(self, methodName='runTest'): - super(TestPermissions, self).__init__(methodName=methodName) - - def setUp(self): - self.u1 = UserModel().create_or_update( - username=u'u1', password=u'qweqwe', - email=u'u1@rhodecode.org', name=u'u1', lastname=u'u1' - ) - self.u2 = UserModel().create_or_update( - username=u'u2', password=u'qweqwe', - email=u'u2@rhodecode.org', name=u'u2', lastname=u'u2' - ) - self.anon = User.get_by_username('default') - self.a1 = UserModel().create_or_update( - username=u'a1', password=u'qweqwe', - email=u'a1@rhodecode.org', name=u'a1', lastname=u'a1', admin=True - ) - Session.commit() - - def tearDown(self): - if hasattr(self, 'test_repo'): - RepoModel().delete(repo=self.test_repo) - UserModel().delete(self.u1) - UserModel().delete(self.u2) - UserModel().delete(self.a1) - if hasattr(self, 'g1'): - ReposGroupModel().delete(self.g1.group_id) - if hasattr(self, 'g2'): - ReposGroupModel().delete(self.g2.group_id) - - if hasattr(self, 'ug1'): - UsersGroupModel().delete(self.ug1, force=True) - - Session.commit() - - def test_default_perms_set(self): - u1_auth = AuthUser(user_id=self.u1.user_id) - perms = { - 'repositories_groups': {}, - 'global': set([u'hg.create.repository', u'repository.read', - u'hg.register.manual_activate']), - 'repositories': {u'vcs_test_hg': u'repository.read'} - } - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], - perms['repositories'][HG_REPO]) - new_perm = 'repository.write' - RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm) - Session.commit() - - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], new_perm) - - def test_default_admin_perms_set(self): - a1_auth = AuthUser(user_id=self.a1.user_id) - perms = { - 'repositories_groups': {}, - 'global': set([u'hg.admin']), - 'repositories': {u'vcs_test_hg': u'repository.admin'} - } - self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], - perms['repositories'][HG_REPO]) - new_perm = 'repository.write' - RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, perm=new_perm) - Session.commit() - # cannot really downgrade admins permissions !? they still get's set as - # admin ! - u1_auth = AuthUser(user_id=self.a1.user_id) - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], - perms['repositories'][HG_REPO]) - - def test_default_group_perms(self): - self.g1 = _make_group('test1', skip_if_exists=True) - self.g2 = _make_group('test2', skip_if_exists=True) - u1_auth = AuthUser(user_id=self.u1.user_id) - perms = { - 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'}, - 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']), - 'repositories': {u'vcs_test_hg': u'repository.read'} - } - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], - perms['repositories'][HG_REPO]) - self.assertEqual(u1_auth.permissions['repositories_groups'], - perms['repositories_groups']) - - def test_default_admin_group_perms(self): - self.g1 = _make_group('test1', skip_if_exists=True) - self.g2 = _make_group('test2', skip_if_exists=True) - a1_auth = AuthUser(user_id=self.a1.user_id) - perms = { - 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'}, - 'global': set(['hg.admin']), - 'repositories': {u'vcs_test_hg': 'repository.admin'} - } - - self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], - perms['repositories'][HG_REPO]) - self.assertEqual(a1_auth.permissions['repositories_groups'], - perms['repositories_groups']) - - def test_propagated_permission_from_users_group(self): - # make group - self.ug1 = UsersGroupModel().create('G1') - # add user to group - UsersGroupModel().add_user_to_group(self.ug1, self.u1) - - # set permission to lower - new_perm = 'repository.none' - RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm) - Session.commit() - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], - new_perm) - - # grant perm for group this should override permission from user - new_perm = 'repository.write' - RepoModel().grant_users_group_permission(repo=HG_REPO, - group_name=self.ug1, - perm=new_perm) - # check perms - u1_auth = AuthUser(user_id=self.u1.user_id) - perms = { - 'repositories_groups': {}, - 'global': set([u'hg.create.repository', u'repository.read', - u'hg.register.manual_activate']), - 'repositories': {u'vcs_test_hg': u'repository.read'} - } - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], - new_perm) - self.assertEqual(u1_auth.permissions['repositories_groups'], - perms['repositories_groups']) - - def test_propagated_permission_from_users_group_lower_weight(self): - # make group - self.ug1 = UsersGroupModel().create('G1') - # add user to group - UsersGroupModel().add_user_to_group(self.ug1, self.u1) - - # set permission to lower - new_perm_h = 'repository.write' - RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, - perm=new_perm_h) - Session.commit() - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], - new_perm_h) - - # grant perm for group this should NOT override permission from user - # since it's lower than granted - new_perm_l = 'repository.read' - RepoModel().grant_users_group_permission(repo=HG_REPO, - group_name=self.ug1, - perm=new_perm_l) - # check perms - u1_auth = AuthUser(user_id=self.u1.user_id) - perms = { - 'repositories_groups': {}, - 'global': set([u'hg.create.repository', u'repository.read', - u'hg.register.manual_activate']), - 'repositories': {u'vcs_test_hg': u'repository.write'} - } - self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], - new_perm_h) - self.assertEqual(u1_auth.permissions['repositories_groups'], - perms['repositories_groups']) - - def test_repo_in_group_permissions(self): - self.g1 = _make_group('group1', skip_if_exists=True) - self.g2 = _make_group('group2', skip_if_exists=True) - Session.commit() - # both perms should be read ! - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories_groups'], - {u'group1': u'group.read', u'group2': u'group.read'}) - - a1_auth = AuthUser(user_id=self.anon.user_id) - self.assertEqual(a1_auth.permissions['repositories_groups'], - {u'group1': u'group.read', u'group2': u'group.read'}) - - #Change perms to none for both groups - ReposGroupModel().grant_user_permission(repos_group=self.g1, - user=self.anon, - perm='group.none') - ReposGroupModel().grant_user_permission(repos_group=self.g2, - user=self.anon, - perm='group.none') - - - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories_groups'], - {u'group1': u'group.none', u'group2': u'group.none'}) - - a1_auth = AuthUser(user_id=self.anon.user_id) - self.assertEqual(a1_auth.permissions['repositories_groups'], - {u'group1': u'group.none', u'group2': u'group.none'}) - - # add repo to group - form_data = { - 'repo_name':HG_REPO, - 'repo_name_full':RepoGroup.url_sep().join([self.g1.group_name,HG_REPO]), - 'repo_type':'hg', - 'clone_uri':'', - 'repo_group':self.g1.group_id, - 'description':'desc', - 'private':False - } - self.test_repo = RepoModel().create(form_data, cur_user=self.u1) - Session.commit() - - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories_groups'], - {u'group1': u'group.none', u'group2': u'group.none'}) - - a1_auth = AuthUser(user_id=self.anon.user_id) - self.assertEqual(a1_auth.permissions['repositories_groups'], - {u'group1': u'group.none', u'group2': u'group.none'}) - - #grant permission for u2 ! - ReposGroupModel().grant_user_permission(repos_group=self.g1, - user=self.u2, - perm='group.read') - ReposGroupModel().grant_user_permission(repos_group=self.g2, - user=self.u2, - perm='group.read') - Session.commit() - self.assertNotEqual(self.u1, self.u2) - #u1 and anon should have not change perms while u2 should ! - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories_groups'], - {u'group1': u'group.none', u'group2': u'group.none'}) - - u2_auth = AuthUser(user_id=self.u2.user_id) - self.assertEqual(u2_auth.permissions['repositories_groups'], - {u'group1': u'group.read', u'group2': u'group.read'}) - - a1_auth = AuthUser(user_id=self.anon.user_id) - self.assertEqual(a1_auth.permissions['repositories_groups'], - {u'group1': u'group.none', u'group2': u'group.none'}) - - def test_repo_group_user_as_user_group_member(self): - # create Group1 - self.g1 = _make_group('group1', skip_if_exists=True) - Session.commit() - a1_auth = AuthUser(user_id=self.anon.user_id) - - self.assertEqual(a1_auth.permissions['repositories_groups'], - {u'group1': u'group.read'}) - - # set default permission to none - ReposGroupModel().grant_user_permission(repos_group=self.g1, - user=self.anon, - perm='group.none') - # make group - self.ug1 = UsersGroupModel().create('G1') - # add user to group - UsersGroupModel().add_user_to_group(self.ug1, self.u1) - Session.commit() - - # check if user is in the group - membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members] - self.assertEqual(membrs, [self.u1.user_id]) - # add some user to that group - - # check his permissions - a1_auth = AuthUser(user_id=self.anon.user_id) - self.assertEqual(a1_auth.permissions['repositories_groups'], - {u'group1': u'group.none'}) - - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories_groups'], - {u'group1': u'group.none'}) - - # grant ug1 read permissions for - ReposGroupModel().grant_users_group_permission(repos_group=self.g1, - group_name=self.ug1, - perm='group.read') - Session.commit() - # check if the - obj = Session.query(UsersGroupRepoGroupToPerm)\ - .filter(UsersGroupRepoGroupToPerm.group == self.g1)\ - .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\ - .scalar() - self.assertEqual(obj.permission.permission_name, 'group.read') - - a1_auth = AuthUser(user_id=self.anon.user_id) - - self.assertEqual(a1_auth.permissions['repositories_groups'], - {u'group1': u'group.none'}) - - u1_auth = AuthUser(user_id=self.u1.user_id) - self.assertEqual(u1_auth.permissions['repositories_groups'], - {u'group1': u'group.read'}) diff --git a/rhodecode/tests/models/test_permissions.py b/rhodecode/tests/models/test_permissions.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/models/test_permissions.py @@ -0,0 +1,438 @@ +import os +import unittest +from rhodecode.tests import * + +from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.model.repo import RepoModel +from rhodecode.model.db import RepoGroup, User, UsersGroupRepoGroupToPerm +from rhodecode.model.user import UserModel + +from rhodecode.model.meta import Session +from rhodecode.model.users_group import UsersGroupModel +from rhodecode.lib.auth import AuthUser + + +def _make_group(path, desc='desc', parent_id=None, + skip_if_exists=False): + + gr = RepoGroup.get_by_group_name(path) + if gr and skip_if_exists: + return gr + + gr = ReposGroupModel().create(path, desc, parent_id) + return gr + + +class TestPermissions(unittest.TestCase): + def __init__(self, methodName='runTest'): + super(TestPermissions, self).__init__(methodName=methodName) + + def setUp(self): + self.u1 = UserModel().create_or_update( + username=u'u1', password=u'qweqwe', + email=u'u1@rhodecode.org', firstname=u'u1', lastname=u'u1' + ) + self.u2 = UserModel().create_or_update( + username=u'u2', password=u'qweqwe', + email=u'u2@rhodecode.org', firstname=u'u2', lastname=u'u2' + ) + self.u3 = UserModel().create_or_update( + username=u'u3', password=u'qweqwe', + email=u'u3@rhodecode.org', firstname=u'u3', lastname=u'u3' + ) + self.anon = User.get_by_username('default') + self.a1 = UserModel().create_or_update( + username=u'a1', password=u'qweqwe', + email=u'a1@rhodecode.org', firstname=u'a1', lastname=u'a1', admin=True + ) + Session().commit() + + def tearDown(self): + if hasattr(self, 'test_repo'): + RepoModel().delete(repo=self.test_repo) + UserModel().delete(self.u1) + UserModel().delete(self.u2) + UserModel().delete(self.u3) + UserModel().delete(self.a1) + if hasattr(self, 'g1'): + ReposGroupModel().delete(self.g1.group_id) + if hasattr(self, 'g2'): + ReposGroupModel().delete(self.g2.group_id) + + if hasattr(self, 'ug1'): + UsersGroupModel().delete(self.ug1, force=True) + + Session().commit() + + def test_default_perms_set(self): + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.create.repository', u'repository.read', + u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.read'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + new_perm = 'repository.write' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, + perm=new_perm) + Session().commit() + + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm) + + def test_default_admin_perms_set(self): + a1_auth = AuthUser(user_id=self.a1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.admin']), + 'repositories': {u'vcs_test_hg': u'repository.admin'} + } + self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + new_perm = 'repository.write' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.a1, + perm=new_perm) + Session().commit() + # cannot really downgrade admins permissions !? they still get's set as + # admin ! + u1_auth = AuthUser(user_id=self.a1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + + def test_default_group_perms(self): + self.g1 = _make_group('test1', skip_if_exists=True) + self.g2 = _make_group('test2', skip_if_exists=True) + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {u'test1': 'group.read', u'test2': 'group.read'}, + 'global': set([u'hg.create.repository', u'repository.read', u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.read'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + self.assertEqual(u1_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_default_admin_group_perms(self): + self.g1 = _make_group('test1', skip_if_exists=True) + self.g2 = _make_group('test2', skip_if_exists=True) + a1_auth = AuthUser(user_id=self.a1.user_id) + perms = { + 'repositories_groups': {u'test1': 'group.admin', u'test2': 'group.admin'}, + 'global': set(['hg.admin']), + 'repositories': {u'vcs_test_hg': 'repository.admin'} + } + + self.assertEqual(a1_auth.permissions['repositories'][HG_REPO], + perms['repositories'][HG_REPO]) + self.assertEqual(a1_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_propagated_permission_from_users_group_by_explicit_perms_exist(self): + # make group + self.ug1 = UsersGroupModel().create('G1') + # add user to group + + UsersGroupModel().add_user_to_group(self.ug1, self.u1) + + # set permission to lower + new_perm = 'repository.none' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm) + Session().commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm) + + # grant perm for group this should not override permission from user + # since it has explicitly set + new_perm_gr = 'repository.write' + RepoModel().grant_users_group_permission(repo=HG_REPO, + group_name=self.ug1, + perm=new_perm_gr) + # check perms + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.create.repository', u'repository.read', + u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.read'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm) + self.assertEqual(u1_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_propagated_permission_from_users_group(self): + # make group + self.ug1 = UsersGroupModel().create('G1') + # add user to group + + UsersGroupModel().add_user_to_group(self.ug1, self.u3) + + # grant perm for group this should override default permission from user + new_perm_gr = 'repository.write' + RepoModel().grant_users_group_permission(repo=HG_REPO, + group_name=self.ug1, + perm=new_perm_gr) + # check perms + u3_auth = AuthUser(user_id=self.u3.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.create.repository', u'repository.read', + u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.read'} + } + self.assertEqual(u3_auth.permissions['repositories'][HG_REPO], + new_perm_gr) + self.assertEqual(u3_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_propagated_permission_from_users_group_lower_weight(self): + # make group + self.ug1 = UsersGroupModel().create('G1') + # add user to group + UsersGroupModel().add_user_to_group(self.ug1, self.u1) + + # set permission to lower + new_perm_h = 'repository.write' + RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, + perm=new_perm_h) + Session().commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm_h) + + # grant perm for group this should NOT override permission from user + # since it's lower than granted + new_perm_l = 'repository.read' + RepoModel().grant_users_group_permission(repo=HG_REPO, + group_name=self.ug1, + perm=new_perm_l) + # check perms + u1_auth = AuthUser(user_id=self.u1.user_id) + perms = { + 'repositories_groups': {}, + 'global': set([u'hg.create.repository', u'repository.read', + u'hg.register.manual_activate']), + 'repositories': {u'vcs_test_hg': u'repository.write'} + } + self.assertEqual(u1_auth.permissions['repositories'][HG_REPO], + new_perm_h) + self.assertEqual(u1_auth.permissions['repositories_groups'], + perms['repositories_groups']) + + def test_repo_in_group_permissions(self): + self.g1 = _make_group('group1', skip_if_exists=True) + self.g2 = _make_group('group2', skip_if_exists=True) + Session().commit() + # both perms should be read ! + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories_groups'], + {u'group1': u'group.read', u'group2': u'group.read'}) + + a1_auth = AuthUser(user_id=self.anon.user_id) + self.assertEqual(a1_auth.permissions['repositories_groups'], + {u'group1': u'group.read', u'group2': u'group.read'}) + + #Change perms to none for both groups + ReposGroupModel().grant_user_permission(repos_group=self.g1, + user=self.anon, + perm='group.none') + ReposGroupModel().grant_user_permission(repos_group=self.g2, + user=self.anon, + perm='group.none') + + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories_groups'], + {u'group1': u'group.none', u'group2': u'group.none'}) + + a1_auth = AuthUser(user_id=self.anon.user_id) + self.assertEqual(a1_auth.permissions['repositories_groups'], + {u'group1': u'group.none', u'group2': u'group.none'}) + + # add repo to group + name = RepoGroup.url_sep().join([self.g1.group_name, 'test_perm']) + self.test_repo = RepoModel().create_repo( + repo_name=name, + repo_type='hg', + description='', + repos_group=self.g1, + owner=self.u1, + ) + Session().commit() + + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories_groups'], + {u'group1': u'group.none', u'group2': u'group.none'}) + + a1_auth = AuthUser(user_id=self.anon.user_id) + self.assertEqual(a1_auth.permissions['repositories_groups'], + {u'group1': u'group.none', u'group2': u'group.none'}) + + #grant permission for u2 ! + ReposGroupModel().grant_user_permission(repos_group=self.g1, + user=self.u2, + perm='group.read') + ReposGroupModel().grant_user_permission(repos_group=self.g2, + user=self.u2, + perm='group.read') + Session().commit() + self.assertNotEqual(self.u1, self.u2) + #u1 and anon should have not change perms while u2 should ! + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories_groups'], + {u'group1': u'group.none', u'group2': u'group.none'}) + + u2_auth = AuthUser(user_id=self.u2.user_id) + self.assertEqual(u2_auth.permissions['repositories_groups'], + {u'group1': u'group.read', u'group2': u'group.read'}) + + a1_auth = AuthUser(user_id=self.anon.user_id) + self.assertEqual(a1_auth.permissions['repositories_groups'], + {u'group1': u'group.none', u'group2': u'group.none'}) + + def test_repo_group_user_as_user_group_member(self): + # create Group1 + self.g1 = _make_group('group1', skip_if_exists=True) + Session().commit() + a1_auth = AuthUser(user_id=self.anon.user_id) + + self.assertEqual(a1_auth.permissions['repositories_groups'], + {u'group1': u'group.read'}) + + # set default permission to none + ReposGroupModel().grant_user_permission(repos_group=self.g1, + user=self.anon, + perm='group.none') + # make group + self.ug1 = UsersGroupModel().create('G1') + # add user to group + UsersGroupModel().add_user_to_group(self.ug1, self.u1) + Session().commit() + + # check if user is in the group + membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members] + self.assertEqual(membrs, [self.u1.user_id]) + # add some user to that group + + # check his permissions + a1_auth = AuthUser(user_id=self.anon.user_id) + self.assertEqual(a1_auth.permissions['repositories_groups'], + {u'group1': u'group.none'}) + + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories_groups'], + {u'group1': u'group.none'}) + + # grant ug1 read permissions for + ReposGroupModel().grant_users_group_permission(repos_group=self.g1, + group_name=self.ug1, + perm='group.read') + Session().commit() + # check if the + obj = Session().query(UsersGroupRepoGroupToPerm)\ + .filter(UsersGroupRepoGroupToPerm.group == self.g1)\ + .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\ + .scalar() + self.assertEqual(obj.permission.permission_name, 'group.read') + + a1_auth = AuthUser(user_id=self.anon.user_id) + + self.assertEqual(a1_auth.permissions['repositories_groups'], + {u'group1': u'group.none'}) + + u1_auth = AuthUser(user_id=self.u1.user_id) + self.assertEqual(u1_auth.permissions['repositories_groups'], + {u'group1': u'group.read'}) + + def test_inherited_permissions_from_default_on_user_enabled(self): + user_model = UserModel() + # enable fork and create on default user + usr = 'default' + user_model.revoke_perm(usr, 'hg.create.none') + user_model.grant_perm(usr, 'hg.create.repository') + user_model.revoke_perm(usr, 'hg.fork.none') + user_model.grant_perm(usr, 'hg.fork.repository') + # make sure inherit flag is turned on + self.u1.inherit_default_permissions = True + Session().commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + # this user will have inherited permissions from default user + self.assertEqual(u1_auth.permissions['global'], + set(['hg.create.repository', 'hg.fork.repository', + 'hg.register.manual_activate', + 'repository.read'])) + + def test_inherited_permissions_from_default_on_user_disabled(self): + user_model = UserModel() + # disable fork and create on default user + usr = 'default' + user_model.revoke_perm(usr, 'hg.create.repository') + user_model.grant_perm(usr, 'hg.create.none') + user_model.revoke_perm(usr, 'hg.fork.repository') + user_model.grant_perm(usr, 'hg.fork.none') + # make sure inherit flag is turned on + self.u1.inherit_default_permissions = True + Session().commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + # this user will have inherited permissions from default user + self.assertEqual(u1_auth.permissions['global'], + set(['hg.create.none', 'hg.fork.none', + 'hg.register.manual_activate', + 'repository.read'])) + + def test_non_inherited_permissions_from_default_on_user_enabled(self): + user_model = UserModel() + # enable fork and create on default user + usr = 'default' + user_model.revoke_perm(usr, 'hg.create.none') + user_model.grant_perm(usr, 'hg.create.repository') + user_model.revoke_perm(usr, 'hg.fork.none') + user_model.grant_perm(usr, 'hg.fork.repository') + + #disable global perms on specific user + user_model.revoke_perm(self.u1, 'hg.create.repository') + user_model.grant_perm(self.u1, 'hg.create.none') + user_model.revoke_perm(self.u1, 'hg.fork.repository') + user_model.grant_perm(self.u1, 'hg.fork.none') + + # make sure inherit flag is turned off + self.u1.inherit_default_permissions = False + Session().commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + # this user will have non inherited permissions from he's + # explicitly set permissions + self.assertEqual(u1_auth.permissions['global'], + set(['hg.create.none', 'hg.fork.none', + 'hg.register.manual_activate', + 'repository.read'])) + + def test_non_inherited_permissions_from_default_on_user_disabled(self): + user_model = UserModel() + # disable fork and create on default user + usr = 'default' + user_model.revoke_perm(usr, 'hg.create.repository') + user_model.grant_perm(usr, 'hg.create.none') + user_model.revoke_perm(usr, 'hg.fork.repository') + user_model.grant_perm(usr, 'hg.fork.none') + + #enable global perms on specific user + user_model.revoke_perm(self.u1, 'hg.create.none') + user_model.grant_perm(self.u1, 'hg.create.repository') + user_model.revoke_perm(self.u1, 'hg.fork.none') + user_model.grant_perm(self.u1, 'hg.fork.repository') + + # make sure inherit flag is turned off + self.u1.inherit_default_permissions = False + Session().commit() + u1_auth = AuthUser(user_id=self.u1.user_id) + # this user will have non inherited permissions from he's + # explicitly set permissions + self.assertEqual(u1_auth.permissions['global'], + set(['hg.create.repository', 'hg.fork.repository', + 'hg.register.manual_activate', + 'repository.read'])) + diff --git a/rhodecode/tests/models/test_repos_groups.py b/rhodecode/tests/models/test_repos_groups.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/models/test_repos_groups.py @@ -0,0 +1,172 @@ +import os +import unittest +from rhodecode.tests import * + +from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.model.repo import RepoModel +from rhodecode.model.db import RepoGroup, User +from rhodecode.model.meta import Session +from sqlalchemy.exc import IntegrityError + + +def _make_group(path, desc='desc', parent_id=None, + skip_if_exists=False): + + gr = RepoGroup.get_by_group_name(path) + if gr and skip_if_exists: + return gr + + gr = ReposGroupModel().create(path, desc, parent_id) + return gr + + +class TestReposGroups(unittest.TestCase): + + def setUp(self): + self.g1 = _make_group('test1', skip_if_exists=True) + Session().commit() + self.g2 = _make_group('test2', skip_if_exists=True) + Session().commit() + self.g3 = _make_group('test3', skip_if_exists=True) + Session().commit() + + def tearDown(self): + print 'out' + + def __check_path(self, *path): + """ + Checks the path for existance ! + """ + path = [TESTS_TMP_PATH] + list(path) + path = os.path.join(*path) + return os.path.isdir(path) + + def _check_folders(self): + print os.listdir(TESTS_TMP_PATH) + + def __delete_group(self, id_): + ReposGroupModel().delete(id_) + + def __update_group(self, id_, path, desc='desc', parent_id=None): + form_data = dict( + group_name=path, + group_description=desc, + group_parent_id=parent_id, + perms_updates=[], + perms_new=[], + enable_locking=False + ) + gr = ReposGroupModel().update(id_, form_data) + return gr + + def test_create_group(self): + g = _make_group('newGroup') + self.assertEqual(g.full_path, 'newGroup') + + self.assertTrue(self.__check_path('newGroup')) + + def test_create_same_name_group(self): + self.assertRaises(IntegrityError, lambda: _make_group('newGroup')) + Session().rollback() + + def test_same_subgroup(self): + sg1 = _make_group('sub1', parent_id=self.g1.group_id) + self.assertEqual(sg1.parent_group, self.g1) + self.assertEqual(sg1.full_path, 'test1/sub1') + self.assertTrue(self.__check_path('test1', 'sub1')) + + ssg1 = _make_group('subsub1', parent_id=sg1.group_id) + self.assertEqual(ssg1.parent_group, sg1) + self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1') + self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1')) + + def test_remove_group(self): + sg1 = _make_group('deleteme') + self.__delete_group(sg1.group_id) + + self.assertEqual(RepoGroup.get(sg1.group_id), None) + self.assertFalse(self.__check_path('deteteme')) + + sg1 = _make_group('deleteme', parent_id=self.g1.group_id) + self.__delete_group(sg1.group_id) + + self.assertEqual(RepoGroup.get(sg1.group_id), None) + self.assertFalse(self.__check_path('test1', 'deteteme')) + + def test_rename_single_group(self): + sg1 = _make_group('initial') + + new_sg1 = self.__update_group(sg1.group_id, 'after') + self.assertTrue(self.__check_path('after')) + self.assertEqual(RepoGroup.get_by_group_name('initial'), None) + + def test_update_group_parent(self): + + sg1 = _make_group('initial', parent_id=self.g1.group_id) + + new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id) + self.assertTrue(self.__check_path('test1', 'after')) + self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None) + + new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id) + self.assertTrue(self.__check_path('test3', 'after')) + self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None) + + new_sg1 = self.__update_group(sg1.group_id, 'hello') + self.assertTrue(self.__check_path('hello')) + + self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1) + + def test_subgrouping_with_repo(self): + + g1 = _make_group('g1') + g2 = _make_group('g2') + + # create new repo + form_data = dict(repo_name='john', + repo_name_full='john', + fork_name=None, + description=None, + repo_group=None, + private=False, + repo_type='hg', + clone_uri=None, + landing_rev='tip', + enable_locking=False) + cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN) + r = RepoModel().create(form_data, cur_user) + + self.assertEqual(r.repo_name, 'john') + + # put repo into group + form_data = form_data + form_data['repo_group'] = g1.group_id + form_data['perms_new'] = [] + form_data['perms_updates'] = [] + RepoModel().update(r.repo_name, form_data) + self.assertEqual(r.repo_name, 'g1/john') + + self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id) + self.assertTrue(self.__check_path('g2', 'g1')) + + # test repo + self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1', + r.just_name])) + + def test_move_to_root(self): + g1 = _make_group('t11') + Session().commit() + g2 = _make_group('t22', parent_id=g1.group_id) + Session().commit() + + self.assertEqual(g2.full_path, 't11/t22') + self.assertTrue(self.__check_path('t11', 't22')) + + g2 = self.__update_group(g2.group_id, 'g22', parent_id=None) + Session().commit() + + self.assertEqual(g2.group_name, 'g22') + # we moved out group from t1 to '' so it's full path should be 'g2' + self.assertEqual(g2.full_path, 'g22') + self.assertFalse(self.__check_path('t11', 't22')) + self.assertTrue(self.__check_path('g22')) diff --git a/rhodecode/tests/models/test_users.py b/rhodecode/tests/models/test_users.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/models/test_users.py @@ -0,0 +1,124 @@ +import unittest +from rhodecode.tests import * + +from rhodecode.model.db import User, UsersGroup, UsersGroupMember, UserEmailMap,\ + Permission +from rhodecode.model.user import UserModel + +from rhodecode.model.meta import Session +from rhodecode.model.users_group import UsersGroupModel + + +class TestUser(unittest.TestCase): + def __init__(self, methodName='runTest'): + Session.remove() + super(TestUser, self).__init__(methodName=methodName) + + def test_create_and_remove(self): + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', + email=u'u232@rhodecode.org', + firstname=u'u1', lastname=u'u1') + Session().commit() + self.assertEqual(User.get_by_username(u'test_user'), usr) + + # make users group + users_group = UsersGroupModel().create('some_example_group') + Session().commit() + + UsersGroupModel().add_user_to_group(users_group, usr) + Session().commit() + + self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group) + self.assertEqual(UsersGroupMember.query().count(), 1) + UserModel().delete(usr.user_id) + Session().commit() + + self.assertEqual(UsersGroupMember.query().all(), []) + + def test_additonal_email_as_main(self): + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', + email=u'main_email@rhodecode.org', + firstname=u'u1', lastname=u'u1') + Session().commit() + + def do(): + m = UserEmailMap() + m.email = u'main_email@rhodecode.org' + m.user = usr + Session().add(m) + Session().commit() + self.assertRaises(AttributeError, do) + + UserModel().delete(usr.user_id) + Session().commit() + + def test_extra_email_map(self): + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', + email=u'main_email@rhodecode.org', + firstname=u'u1', lastname=u'u1') + Session().commit() + + m = UserEmailMap() + m.email = u'main_email2@rhodecode.org' + m.user = usr + Session().add(m) + Session().commit() + + u = User.get_by_email(email='main_email@rhodecode.org') + self.assertEqual(usr.user_id, u.user_id) + self.assertEqual(usr.username, u.username) + + u = User.get_by_email(email='main_email2@rhodecode.org') + self.assertEqual(usr.user_id, u.user_id) + self.assertEqual(usr.username, u.username) + u = User.get_by_email(email='main_email3@rhodecode.org') + self.assertEqual(None, u) + + UserModel().delete(usr.user_id) + Session().commit() + + +class TestUsers(unittest.TestCase): + + def __init__(self, methodName='runTest'): + super(TestUsers, self).__init__(methodName=methodName) + + def setUp(self): + self.u1 = UserModel().create_or_update(username=u'u1', + password=u'qweqwe', + email=u'u1@rhodecode.org', + firstname=u'u1', lastname=u'u1') + + def tearDown(self): + perm = Permission.query().all() + for p in perm: + UserModel().revoke_perm(self.u1, p) + + UserModel().delete(self.u1) + Session().commit() + + def test_add_perm(self): + perm = Permission.query().all()[0] + UserModel().grant_perm(self.u1, perm) + Session().commit() + self.assertEqual(UserModel().has_perm(self.u1, perm), True) + + def test_has_perm(self): + perm = Permission.query().all() + for p in perm: + has_p = UserModel().has_perm(self.u1, p) + self.assertEqual(False, has_p) + + def test_revoke_perm(self): + perm = Permission.query().all()[0] + UserModel().grant_perm(self.u1, perm) + Session().commit() + self.assertEqual(UserModel().has_perm(self.u1, perm), True) + + #revoke + UserModel().revoke_perm(self.u1, perm) + Session().commit() + self.assertEqual(UserModel().has_perm(self.u1, perm), False) diff --git a/rhodecode/tests/nose_parametrized.py b/rhodecode/tests/nose_parametrized.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/nose_parametrized.py @@ -0,0 +1,238 @@ +import re +import new +import inspect +import logging +import logging.handlers +from functools import wraps + +from nose.tools import nottest +from unittest import TestCase + + +def _terrible_magic_get_defining_classes(): + """ Returns the set of parent classes of the class currently being defined. + Will likely only work if called from the ``parameterized`` decorator. + This function is entirely @brandon_rhodes's fault, as he suggested + the implementation: http://stackoverflow.com/a/8793684/71522 + """ + stack = inspect.stack() + if len(stack) <= 4: + return [] + frame = stack[3] + code_context = frame[4][0].strip() + if not code_context.startswith("class "): + return [] + _, parents = code_context.split("(", 1) + parents, _ = parents.rsplit(")", 1) + return eval("[" + parents + "]", frame[0].f_globals, frame[0].f_locals) + + +def parameterized(input): + """ Parameterize a test case: + >>> add1_tests = [(1, 2), (2, 3)] + >>> class TestFoo(object): + ... @parameterized(add1_tests) + ... def test_add1(self, input, expected): + ... assert_equal(add1(input), expected) + >>> @parameterized(add1_tests) + ... def test_add1(input, expected): + ... assert_equal(add1(input), expected) + >>> + """ + + if not hasattr(input, "__iter__"): + raise ValueError("expected iterable input; got %r" % (input,)) + + def parameterized_helper(f): + attached_instance_method = [False] + + parent_classes = _terrible_magic_get_defining_classes() + if any(issubclass(cls, TestCase) for cls in parent_classes): + raise Exception("Warning: '@parameterized' tests won't work " + "inside subclasses of 'TestCase' - use " + "'@parameterized.expand' instead") + + @wraps(f) + def parameterized_helper_method(self=None): + if self is not None and not attached_instance_method[0]: + # confusingly, we need to create a named instance method and + # attach that to the class... + cls = self.__class__ + im_f = new.instancemethod(f, None, cls) + setattr(cls, f.__name__, im_f) + attached_instance_method[0] = True + for args in input: + if isinstance(args, basestring): + args = [args] + # ... then pull that named instance method off, turning it into + # a bound method ... + if self is not None: + args = [getattr(self, f.__name__)] + list(args) + else: + args = [f] + list(args) + # ... then yield that as a tuple. If those steps aren't + # followed precicely, Nose gets upset and doesn't run the test + # or doesn't run setup methods. + yield tuple(args) + + f.__name__ = "_helper_for_%s" % (f.__name__,) + parameterized_helper_method.parameterized_input = input + parameterized_helper_method.parameterized_func = f + return parameterized_helper_method + + return parameterized_helper + + +def to_safe_name(s): + return re.sub("[^a-zA-Z0-9_]", "", s) + + +def parameterized_expand_helper(func_name, func, args): + def parameterized_expand_helper_helper(self=()): + if self != (): + self = (self,) + return func(*(self + args)) + parameterized_expand_helper_helper.__name__ = func_name + return parameterized_expand_helper_helper + + +def parameterized_expand(input): + """ A "brute force" method of parameterizing test cases. Creates new test + cases and injects them into the namespace that the wrapped function + is being defined in. Useful for parameterizing tests in subclasses + of 'UnitTest', where Nose test generators don't work. + + >>> @parameterized.expand([("foo", 1, 2)]) + ... def test_add1(name, input, expected): + ... actual = add1(input) + ... assert_equal(actual, expected) + ... + >>> locals() + ... 'test_add1_foo_0': ... + >>> + """ + + def parameterized_expand_wrapper(f): + stack = inspect.stack() + frame = stack[1] + frame_locals = frame[0].f_locals + + base_name = f.__name__ + for num, args in enumerate(input): + name_suffix = "_%s" % (num,) + if len(args) > 0 and isinstance(args[0], basestring): + name_suffix += "_" + to_safe_name(args[0]) + name = base_name + name_suffix + new_func = parameterized_expand_helper(name, f, args) + frame_locals[name] = new_func + return nottest(f) + return parameterized_expand_wrapper + +parameterized.expand = parameterized_expand + + +def assert_contains(haystack, needle): + if needle not in haystack: + raise AssertionError("%r not in %r" % (needle, haystack)) + + +def assert_not_contains(haystack, needle): + if needle in haystack: + raise AssertionError("%r in %r" % (needle, haystack)) + + +def imported_from_test(): + """ Returns true if it looks like this module is being imported by unittest + or nose. """ + import re + import inspect + nose_re = re.compile(r"\bnose\b") + unittest_re = re.compile(r"\bunittest2?\b") + for frame in inspect.stack(): + file = frame[1] + if nose_re.search(file) or unittest_re.search(file): + return True + return False + + +def assert_raises(func, exc_type, str_contains=None, repr_contains=None): + try: + func() + except exc_type, e: + if str_contains is not None and str_contains not in str(e): + raise AssertionError("%s raised, but %r does not contain %r" + % (exc_type, str(e), str_contains)) + if repr_contains is not None and repr_contains not in repr(e): + raise AssertionError("%s raised, but %r does not contain %r" + % (exc_type, repr(e), repr_contains)) + return e + else: + raise AssertionError("%s not raised" % (exc_type,)) + + +log_handler = None + + +def setup_logging(): + """ Configures a log handler which will capure log messages during a test. + The ``logged_messages`` and ``assert_no_errors_logged`` functions can be + used to make assertions about these logged messages. + + For example:: + + from ensi_common.testing import ( + setup_logging, teardown_logging, assert_no_errors_logged, + assert_logged, + ) + + class TestWidget(object): + def setup(self): + setup_logging() + + def teardown(self): + assert_no_errors_logged() + teardown_logging() + + def test_that_will_fail(self): + log.warning("this warning message will trigger a failure") + + def test_that_will_pass(self): + log.info("but info messages are ok") + assert_logged("info messages are ok") + """ + + global log_handler + if log_handler is not None: + logging.getLogger().removeHandler(log_handler) + log_handler = logging.handlers.BufferingHandler(1000) + formatter = logging.Formatter("%(name)s: %(levelname)s: %(message)s") + log_handler.setFormatter(formatter) + logging.getLogger().addHandler(log_handler) + + +def teardown_logging(): + global log_handler + if log_handler is not None: + logging.getLogger().removeHandler(log_handler) + log_handler = None + + +def logged_messages(): + assert log_handler, "setup_logging not called" + return [(log_handler.format(record), record) for record in log_handler.buffer] + + +def assert_no_errors_logged(): + for _, record in logged_messages(): + if record.levelno >= logging.WARNING: + # Assume that the nose log capture plugin is being used, so it will + # show the exception. + raise AssertionError("an unexpected error was logged") + + +def assert_logged(expected_msg_contents): + for msg, _ in logged_messages(): + if expected_msg_contents in msg: + return + raise AssertionError("no logged message contains %r" + % (expected_msg_contents,)) diff --git a/rhodecode/tests/scripts/create_rc.sh b/rhodecode/tests/scripts/create_rc.sh new file mode 100755 --- /dev/null +++ b/rhodecode/tests/scripts/create_rc.sh @@ -0,0 +1,12 @@ +psql -U postgres -h localhost -c 'drop database if exists rhodecode;' +psql -U postgres -h localhost -c 'create database rhodecode;' +paster setup-rhodecode rc.ini -q --user=marcink --password=qweqwe --email=marcin@python-blog.com --repos=/home/marcink/repos +API_KEY=`psql -R " " -A -U postgres -h localhost -c "select api_key from users where admin=TRUE" -d rhodecode | awk '{print $2}'` +echo "run those after running server" +echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo1 password:qweqwe email:demo1@rhodecode.org" +echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo2 password:qweqwe email:demo2@rhodecode.org" +echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_user username:demo3 password:qweqwe email:demo3@rhodecode.org" +echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 create_users_group group_name:demo12" +echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_users_group usersgroupid:demo12 userid:demo1" +echo "rhodecode-api --apikey=$API_KEY --apihost=http://127.0.0.1:5001 add_user_to_users_group usersgroupid:demo12 userid:demo2" + diff --git a/rhodecode/tests/mem_watch b/rhodecode/tests/scripts/mem_watch rename from rhodecode/tests/mem_watch rename to rhodecode/tests/scripts/mem_watch diff --git a/rhodecode/tests/_test_concurency.py b/rhodecode/tests/scripts/test_concurency.py rename from rhodecode/tests/_test_concurency.py rename to rhodecode/tests/scripts/test_concurency.py --- a/rhodecode/tests/_test_concurency.py +++ b/rhodecode/tests/scripts/test_concurency.py @@ -79,6 +79,7 @@ class Command(object): print stdout, stderr return stdout, stderr + def get_session(): engine = engine_from_config(conf, 'sqlalchemy.db1.') init_model(engine) @@ -124,7 +125,6 @@ def create_test_repo(force=True): if user is None: raise Exception('user not found') - repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar() if repo is None: @@ -140,6 +140,7 @@ def create_test_repo(force=True): print 'done' + def set_anonymous_access(enable=True): sa = get_session() user = sa.query(User).filter(User.username == 'default').one() @@ -147,6 +148,7 @@ def set_anonymous_access(enable=True): sa.add(user) sa.commit() + def get_anonymous_access(): sa = get_session() return sa.query(User).filter(User.username == 'default').one().active diff --git a/rhodecode/tests/rhodecode_crawler.py b/rhodecode/tests/scripts/test_crawler.py rename from rhodecode/tests/rhodecode_crawler.py rename to rhodecode/tests/scripts/test_crawler.py diff --git a/rhodecode/tests/test_hg_operations.py b/rhodecode/tests/scripts/test_vcs_operations.py rename from rhodecode/tests/test_hg_operations.py rename to rhodecode/tests/scripts/test_vcs_operations.py --- a/rhodecode/tests/test_hg_operations.py +++ b/rhodecode/tests/scripts/test_vcs_operations.py @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- """ - rhodecode.tests.test_hg_operations - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + rhodecode.tests.test_scm_operations + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - Test suite for making push/pull operations + Test suite for making push/pull operations. + Run using:: + + RC_WHOOSH_TEST_DISABLE=1 RC_NO_TMP_PATH=1 nosetests rhodecode/tests/scripts/test_vcs_operations.py :created_on: Dec 30, 2010 :author: marcink @@ -24,47 +27,22 @@ # along with this program. If not, see . import os -import time -import sys -import shutil -import logging - +import tempfile +import unittest from os.path import join as jn from os.path import dirname as dn from tempfile import _RandomNameSequence from subprocess import Popen, PIPE -from paste.deploy import appconfig -from pylons import config -from sqlalchemy import engine_from_config - -from rhodecode.lib.utils import add_cache -from rhodecode.model import init_model -from rhodecode.model import meta +from rhodecode.tests import * from rhodecode.model.db import User, Repository, UserLog -from rhodecode.lib.auth import get_crypt_password - -from rhodecode.tests import TESTS_TMP_PATH, NEW_HG_REPO, HG_REPO -from rhodecode.config.environment import load_environment - -rel_path = dn(dn(dn(os.path.abspath(__file__)))) +from rhodecode.model.meta import Session +from rhodecode.model.repo import RepoModel -conf = appconfig('config:%s' % sys.argv[1], relative_to=rel_path) -load_environment(conf.global_conf, conf.local_conf) - -add_cache(conf) +DEBUG = True +HOST = '127.0.0.1:5000' # test host -USER = 'test_admin' -PASS = 'test12' -HOST = '127.0.0.1:5000' -DEBUG = False -print 'DEBUG:', DEBUG -log = logging.getLogger(__name__) - -engine = engine_from_config(conf, 'sqlalchemy.db1.') -init_model(engine) -sa = meta.Session class Command(object): @@ -72,13 +50,13 @@ class Command(object): self.cwd = cwd def execute(self, cmd, *args): - """Runs command on the system with given ``args``. + """ + Runs command on the system with given ``args``. """ command = cmd + ' ' + ' '.join(args) - log.debug('Executing %s' % command) if DEBUG: - print command + print '*** CMD %s ***' % command p = Popen(command, shell=True, stdout=PIPE, stderr=PIPE, cwd=self.cwd) stdout, stderr = p.communicate() if DEBUG: @@ -86,316 +64,362 @@ class Command(object): return stdout, stderr -def test_wrapp(func): +def _get_tmp_dir(): + return tempfile.mkdtemp(prefix='rc_integration_test') + - def __wrapp(*args, **kwargs): - print '>>>%s' % func.__name__ - try: - res = func(*args, **kwargs) - except Exception, e: - print ('###############\n-' - '--%s failed %s--\n' - '###############\n' % (func.__name__, e)) - sys.exit() - print '++OK++' - return res - return __wrapp +def _construct_url(repo, dest=None, **kwargs): + if dest is None: + #make temp clone + dest = _get_tmp_dir() + params = { + 'user': TEST_USER_ADMIN_LOGIN, + 'passwd': TEST_USER_ADMIN_PASS, + 'host': HOST, + 'cloned_repo': repo, + 'dest': dest + } + params.update(**kwargs) + if params['user'] and params['passwd']: + _url = 'http://%(user)s:%(passwd)s@%(host)s/%(cloned_repo)s %(dest)s' % params + else: + _url = 'http://(host)s/%(cloned_repo)s %(dest)s' % params + return _url -def create_test_user(force=True): - print '\tcreating test user' - - user = User.get_by_username(USER) +def _add_files_and_push(vcs, DEST, **kwargs): + """ + Generate some files, add it to DEST repo and push back + vcs is git or hg and defines what VCS we want to make those files for - if force and user is not None: - print '\tremoving current user' - for repo in Repository.query().filter(Repository.user == user).all(): - sa.delete(repo) - sa.delete(user) - sa.commit() + :param vcs: + :param DEST: + """ + # commit some stuff into this repo + cwd = path = jn(DEST) + #added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next()) + added_file = jn(path, '%ssetup.py' % _RandomNameSequence().next()) + Command(cwd).execute('touch %s' % added_file) + Command(cwd).execute('%s add %s' % (vcs, added_file)) - if user is None or force: - print '\tcreating new one' - new_usr = User() - new_usr.username = USER - new_usr.password = get_crypt_password(PASS) - new_usr.email = 'mail@mail.com' - new_usr.name = 'test' - new_usr.lastname = 'lasttestname' - new_usr.active = True - new_usr.admin = True - sa.add(new_usr) - sa.commit() - - print '\tdone' - + for i in xrange(3): + cmd = """echo 'added_line%s' >> %s""" % (i, added_file) + Command(cwd).execute(cmd) + if vcs == 'hg': + cmd = """hg commit -m 'commited new %s' -u '%s' %s """ % ( + i, 'Marcin Kuźminski ', added_file + ) + elif vcs == 'git': + cmd = """git ci -m 'commited new %s' --author '%s' %s """ % ( + i, 'Marcin Kuźminski ', added_file + ) + Command(cwd).execute(cmd) + # PUSH it back + if vcs == 'hg': + _REPO = HG_REPO + elif vcs == 'git': + _REPO = GIT_REPO -def create_test_repo(force=True): - from rhodecode.model.repo import RepoModel - - user = User.get_by_username(USER) - if user is None: - raise Exception('user not found') - - - repo = sa.query(Repository).filter(Repository.repo_name == HG_REPO).scalar() + kwargs['dest'] = '' + clone_url = _construct_url(_REPO, **kwargs) + if 'clone_url' in kwargs: + clone_url = kwargs['clone_url'] + if vcs == 'hg': + stdout, stderr = Command(cwd).execute('hg push --verbose', clone_url) + elif vcs == 'git': + stdout, stderr = Command(cwd).execute('git push', clone_url + " master") - if repo is None: - print '\trepo not found creating' - - form_data = {'repo_name':HG_REPO, - 'repo_type':'hg', - 'private':False, - 'clone_uri':'' } - rm = RepoModel(sa) - rm.base_path = '/home/hg' - rm.create(form_data, user) + return stdout, stderr def set_anonymous_access(enable=True): - user = User.get_by_username('default') + user = User.get_by_username(User.DEFAULT_USER) user.active = enable - sa.add(user) - sa.commit() + Session().add(user) + Session().commit() print '\tanonymous access is now:', enable - if enable != User.get_by_username('default').active: + if enable != User.get_by_username(User.DEFAULT_USER).active: raise Exception('Cannot set anonymous access') -def get_anonymous_access(): - user = User.get_by_username('default') - return user.active - #============================================================================== # TESTS #============================================================================== -@test_wrapp -def test_clone_with_credentials(no_errors=False): - cwd = path = jn(TESTS_TMP_PATH, HG_REPO) + +class TestVCSOperations(unittest.TestCase): - try: - shutil.rmtree(path, ignore_errors=True) - os.makedirs(path) - #print 'made dirs %s' % jn(path) - except OSError: - raise + @classmethod + def setup_class(cls): + #DISABLE ANONYMOUS ACCESS + set_anonymous_access(False) + + def setUp(self): + r = Repository.get_by_repo_name(GIT_REPO) + Repository.unlock(r) + r.enable_locking = False + Session().add(r) + Session().commit() - print '\tchecking if anonymous access is enabled' - anonymous_access = get_anonymous_access() - if anonymous_access: - print '\tenabled, disabling it ' - set_anonymous_access(enable=False) + r = Repository.get_by_repo_name(HG_REPO) + Repository.unlock(r) + r.enable_locking = False + Session().add(r) + Session().commit() - clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \ - {'user':USER, - 'pass':PASS, - 'host':HOST, - 'cloned_repo':HG_REPO, - 'dest':path} + def test_clone_hg_repo_by_admin(self): + clone_url = _construct_url(HG_REPO) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) - stdout, stderr = Command(cwd).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 == '' - if no_errors is False: - assert """adding file changes""" in stdout, 'no messages about cloning' - assert """abort""" not in stderr , 'got error from clone' + def test_clone_git_repo_by_admin(self): + clone_url = _construct_url(GIT_REPO) + stdout, stderr = Command('/tmp').execute('git clone', clone_url) + assert 'Cloning into' in stdout + assert stderr == '' -@test_wrapp -def test_clone_anonymous(): - cwd = path = jn(TESTS_TMP_PATH, HG_REPO) + def test_clone_wrong_credentials_hg(self): + clone_url = _construct_url(HG_REPO, passwd='bad!') + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) + assert 'abort: authorization failed' in stderr - try: - shutil.rmtree(path, ignore_errors=True) - os.makedirs(path) - #print 'made dirs %s' % jn(path) - except OSError: - raise + def test_clone_wrong_credentials_git(self): + clone_url = _construct_url(GIT_REPO, passwd='bad!') + stdout, stderr = Command('/tmp').execute('git clone', clone_url) + assert 'fatal: Authentication failed' in stderr + def test_clone_git_dir_as_hg(self): + clone_url = _construct_url(GIT_REPO) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) + assert 'HTTP Error 404: Not Found' in stderr - print '\tchecking if anonymous access is enabled' - anonymous_access = get_anonymous_access() - if not anonymous_access: - print '\tnot enabled, enabling it ' - set_anonymous_access(enable=True) + def test_clone_hg_repo_as_git(self): + clone_url = _construct_url(HG_REPO) + stdout, stderr = Command('/tmp').execute('git clone', clone_url) + assert 'not found:' in stderr - clone_url = 'http://%(host)s/%(cloned_repo)s %(dest)s' % \ - {'user':USER, - 'pass':PASS, - 'host':HOST, - 'cloned_repo':HG_REPO, - 'dest':path} + def test_clone_non_existing_path_hg(self): + clone_url = _construct_url('trololo') + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) + assert 'HTTP Error 404: Not Found' in stderr - stdout, stderr = Command(cwd).execute('hg clone', clone_url) - - assert """adding file changes""" in stdout, 'no messages about cloning' - assert """abort""" not in stderr , 'got error from clone' + def test_clone_non_existing_path_git(self): + clone_url = _construct_url('trololo') + stdout, stderr = Command('/tmp').execute('git clone', clone_url) + assert 'not found:' in stderr - #disable if it was enabled - if not anonymous_access: - print '\tdisabling anonymous access' - set_anonymous_access(enable=False) + def test_push_new_file_hg(self): + DEST = _get_tmp_dir() + clone_url = _construct_url(HG_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) + + stdout, stderr = _add_files_and_push('hg', DEST) + + assert 'pushing to' in stdout + assert 'Repository size' in stdout + assert 'Last revision is now' in stdout -@test_wrapp -def test_clone_wrong_credentials(): - cwd = path = jn(TESTS_TMP_PATH, HG_REPO) + def test_push_new_file_git(self): + DEST = _get_tmp_dir() + clone_url = _construct_url(GIT_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('git clone', clone_url) + + # commit some stuff into this repo + stdout, stderr = _add_files_and_push('git', DEST) - try: - shutil.rmtree(path, ignore_errors=True) - os.makedirs(path) - #print 'made dirs %s' % jn(path) - except OSError: - raise + #WTF git stderr ?! + assert 'master -> master' in stderr - print '\tchecking if anonymous access is enabled' - anonymous_access = get_anonymous_access() - if anonymous_access: - print '\tenabled, disabling it ' - set_anonymous_access(enable=False) + def test_push_wrong_credentials_hg(self): + DEST = _get_tmp_dir() + clone_url = _construct_url(HG_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) - clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s %(dest)s' % \ - {'user':USER + 'error', - 'pass':PASS, - 'host':HOST, - 'cloned_repo':HG_REPO, - 'dest':path} + stdout, stderr = _add_files_and_push('hg', DEST, user='bad', + passwd='name') + + assert 'abort: authorization failed' in stderr - stdout, stderr = Command(cwd).execute('hg clone', clone_url) + def test_push_wrong_credentials_git(self): + DEST = _get_tmp_dir() + clone_url = _construct_url(GIT_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('git clone', clone_url) - if not """abort: authorization failed""" in stderr: - raise Exception('Failure') + stdout, stderr = _add_files_and_push('git', DEST, user='bad', + passwd='name') -@test_wrapp -def test_pull(): - pass + assert 'fatal: Authentication failed' in stderr -@test_wrapp -def test_push_modify_file(f_name='setup.py'): - cwd = path = jn(TESTS_TMP_PATH, HG_REPO) - modified_file = jn(TESTS_TMP_PATH, HG_REPO, f_name) - for i in xrange(5): - cmd = """echo 'added_line%s' >> %s""" % (i, modified_file) - Command(cwd).execute(cmd) + def test_push_back_to_wrong_url_hg(self): + DEST = _get_tmp_dir() + clone_url = _construct_url(HG_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) + + stdout, stderr = _add_files_and_push('hg', DEST, + clone_url='http://127.0.0.1:5000/tmp',) + + assert 'HTTP Error 404: Not Found' in stderr - cmd = """hg ci -m 'changed file %s' %s """ % (i, modified_file) - Command(cwd).execute(cmd) + def test_push_back_to_wrong_url_git(self): + DEST = _get_tmp_dir() + clone_url = _construct_url(GIT_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('git clone', clone_url) - Command(cwd).execute('hg push %s' % jn(TESTS_TMP_PATH, HG_REPO)) + stdout, stderr = _add_files_and_push('git', DEST, + clone_url='http://127.0.0.1:5000/tmp',) -@test_wrapp -def test_push_new_file(commits=15, with_clone=True): + assert 'not found:' in stderr - if with_clone: - test_clone_with_credentials(no_errors=True) + def test_clone_and_create_lock_hg(self): + # enable locking + r = Repository.get_by_repo_name(HG_REPO) + r.enable_locking = True + Session().add(r) + Session().commit() + # clone + clone_url = _construct_url(HG_REPO) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) - cwd = path = jn(TESTS_TMP_PATH, HG_REPO) - added_file = jn(path, '%ssetupążźć.py' % _RandomNameSequence().next()) - - Command(cwd).execute('touch %s' % added_file) - - Command(cwd).execute('hg add %s' % added_file) + #check if lock was made + r = Repository.get_by_repo_name(HG_REPO) + assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id - for i in xrange(commits): - cmd = """echo 'added_line%s' >> %s""" % (i, added_file) - Command(cwd).execute(cmd) + def test_clone_and_create_lock_git(self): + # enable locking + r = Repository.get_by_repo_name(GIT_REPO) + r.enable_locking = True + Session().add(r) + Session().commit() + # clone + clone_url = _construct_url(GIT_REPO) + stdout, stderr = Command('/tmp').execute('git clone', clone_url) - cmd = """hg ci -m 'commited new %s' -u '%s' %s """ % (i, - 'Marcin Kuźminski ', - added_file) - Command(cwd).execute(cmd) - - push_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \ - {'user':USER, - 'pass':PASS, - 'host':HOST, - 'cloned_repo':HG_REPO, - 'dest':jn(TESTS_TMP_PATH, HG_REPO)} + #check if lock was made + r = Repository.get_by_repo_name(GIT_REPO) + assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id - Command(cwd).execute('hg push --verbose --debug %s' % push_url) - -@test_wrapp -def test_push_wrong_credentials(): - cwd = path = jn(TESTS_TMP_PATH, HG_REPO) - clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \ - {'user':USER + 'xxx', - 'pass':PASS, - 'host':HOST, - 'cloned_repo':HG_REPO, - 'dest':jn(TESTS_TMP_PATH, HG_REPO)} + def test_clone_after_repo_was_locked_hg(self): + #lock repo + r = Repository.get_by_repo_name(HG_REPO) + Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) + #pull fails since repo is locked + clone_url = _construct_url(HG_REPO) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) + msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`""" + % (HG_REPO, TEST_USER_ADMIN_LOGIN)) + assert msg in stderr - modified_file = jn(TESTS_TMP_PATH, HG_REPO, 'setup.py') - for i in xrange(5): - cmd = """echo 'added_line%s' >> %s""" % (i, modified_file) - Command(cwd).execute(cmd) + def test_clone_after_repo_was_locked_git(self): + #lock repo + r = Repository.get_by_repo_name(GIT_REPO) + Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) + #pull fails since repo is locked + clone_url = _construct_url(GIT_REPO) + stdout, stderr = Command('/tmp').execute('git clone', clone_url) + msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`""" + % (GIT_REPO, TEST_USER_ADMIN_LOGIN)) + #TODO: fix this somehow later on GIT, GIT is stupid and even if we throw + # back 423 to it, it makes ANOTHER request and we fail there with 405 :/ + msg = "405 Method Not Allowed" + assert msg in stderr - cmd = """hg ci -m 'commited %s' %s """ % (i, modified_file) - Command(cwd).execute(cmd) - - Command(cwd).execute('hg push %s' % clone_url) + def test_push_on_locked_repo_by_other_user_hg(self): + #clone some temp + DEST = _get_tmp_dir() + clone_url = _construct_url(HG_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) -@test_wrapp -def test_push_wrong_path(): - cwd = path = jn(TESTS_TMP_PATH, HG_REPO) - added_file = jn(path, 'somefile.py') + #lock repo + r = Repository.get_by_repo_name(HG_REPO) + # let this user actually push ! + RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, + perm='repository.write') + Session().commit() + Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) - try: - shutil.rmtree(path, ignore_errors=True) - os.makedirs(path) - print '\tmade dirs %s' % jn(path) - except OSError: - raise - - Command(cwd).execute("""echo '' > %s""" % added_file) - Command(cwd).execute("""hg init %s""" % path) - Command(cwd).execute("""hg add %s""" % added_file) + #push fails repo is locked by other user ! + stdout, stderr = _add_files_and_push('hg', DEST, + user=TEST_USER_REGULAR_LOGIN, + passwd=TEST_USER_REGULAR_PASS) + msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`""" + % (HG_REPO, TEST_USER_ADMIN_LOGIN)) + assert msg in stderr - for i in xrange(2): - cmd = """echo 'added_line%s' >> %s""" % (i, added_file) - Command(cwd).execute(cmd) - - cmd = """hg ci -m 'commited new %s' %s """ % (i, added_file) - Command(cwd).execute(cmd) - - clone_url = 'http://%(user)s:%(pass)s@%(host)s/%(cloned_repo)s' % \ - {'user':USER, - 'pass':PASS, - 'host':HOST, - 'cloned_repo':HG_REPO + '_error', - 'dest':jn(TESTS_TMP_PATH, HG_REPO)} - - stdout, stderr = Command(cwd).execute('hg push %s' % clone_url) - if not """abort: HTTP Error 403: Forbidden""" in stderr: - raise Exception('Failure') +#TODO: fix me ! somehow during tests hooks don't get called on GIT +# def test_push_on_locked_repo_by_other_user_git(self): +# #clone some temp +# DEST = _get_tmp_dir() +# clone_url = _construct_url(GIT_REPO, dest=DEST) +# stdout, stderr = Command('/tmp').execute('git clone', clone_url) +# +# #lock repo +# r = Repository.get_by_repo_name(GIT_REPO) +# # let this user actually push ! +# RepoModel().grant_user_permission(repo=r, user=TEST_USER_REGULAR_LOGIN, +# perm='repository.write') +# Session().commit() +# Repository.lock(r, User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id) +# +# #push fails repo is locked by other user ! +# stdout, stderr = _add_files_and_push('git', DEST, +# user=TEST_USER_REGULAR_LOGIN, +# passwd=TEST_USER_REGULAR_PASS) +# msg = ("""abort: HTTP Error 423: Repository `%s` locked by user `%s`""" +# % (GIT_REPO, TEST_USER_ADMIN_LOGIN)) +# #TODO: fix this somehow later on GIT, GIT is stupid and even if we throw +# # back 423 to it, it makes ANOTHER request and we fail there with 405 :/ +# msg = "405 Method Not Allowed" +# assert msg in stderr -@test_wrapp -def get_logs(): - return UserLog.query().all() + def test_push_unlocks_repository_hg(self): + # enable locking + r = Repository.get_by_repo_name(HG_REPO) + r.enable_locking = True + Session().add(r) + Session().commit() + #clone some temp + DEST = _get_tmp_dir() + clone_url = _construct_url(HG_REPO, dest=DEST) + stdout, stderr = Command('/tmp').execute('hg clone', clone_url) -@test_wrapp -def test_logs(initial): - logs = UserLog.query().all() - operations = 4 - if len(initial) + operations != len(logs): - raise Exception("missing number of logs initial:%s vs current:%s" % \ - (len(initial), len(logs))) + #check for lock repo after clone + r = Repository.get_by_repo_name(HG_REPO) + assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id - -if __name__ == '__main__': - create_test_user(force=False) - create_test_repo() + #push is ok and repo is now unlocked + stdout, stderr = _add_files_and_push('hg', DEST) + assert ('remote: Released lock on repo `%s`' % HG_REPO) in stdout + #we need to cleanup the Session Here ! + Session.remove() + r = Repository.get_by_repo_name(HG_REPO) + assert r.locked == [None, None] - initial_logs = get_logs() - print 'initial activity logs: %s' % len(initial_logs) - s = time.time() - #test_push_modify_file() - test_clone_with_credentials() - test_clone_wrong_credentials() - - test_push_new_file(commits=2, with_clone=True) - - test_clone_anonymous() - test_push_wrong_path() - - test_push_wrong_credentials() - - test_logs(initial_logs) - print 'finished ok in %.3f' % (time.time() - s) +#TODO: fix me ! somehow during tests hooks don't get called on GIT +# def test_push_unlocks_repository_git(self): +# # enable locking +# r = Repository.get_by_repo_name(GIT_REPO) +# r.enable_locking = True +# Session().add(r) +# Session().commit() +# #clone some temp +# DEST = _get_tmp_dir() +# clone_url = _construct_url(GIT_REPO, dest=DEST) +# stdout, stderr = Command('/tmp').execute('git clone', clone_url) +# +# #check for lock repo after clone +# r = Repository.get_by_repo_name(GIT_REPO) +# assert r.locked[0] == User.get_by_username(TEST_USER_ADMIN_LOGIN).user_id +# +# #push is ok and repo is now unlocked +# stdout, stderr = _add_files_and_push('git', DEST) +# #assert ('remote: Released lock on repo `%s`' % GIT_REPO) in stdout +# #we need to cleanup the Session Here ! +# Session.remove() +# r = Repository.get_by_repo_name(GIT_REPO) +# assert r.locked == [None, None] diff --git a/rhodecode/tests/test_libs.py b/rhodecode/tests/test_libs.py --- a/rhodecode/tests/test_libs.py +++ b/rhodecode/tests/test_libs.py @@ -23,9 +23,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - - import unittest +import datetime +import hashlib +import mock from rhodecode.tests import * proto = 'http' @@ -116,3 +117,61 @@ class TestLibs(unittest.TestCase): 'marian.user', 'marco-polo', 'marco_polo' ], key=lambda k: k.lower()) self.assertEqual(s, extract_mentioned_users(sample)) + + def test_age(self): + import calendar + from rhodecode.lib.utils2 import age + n = datetime.datetime.now() + delt = lambda *args, **kwargs: datetime.timedelta(*args, **kwargs) + 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] + 2))), + u'1 month and 2 days ago') + self.assertEqual(age(n - delt(hours=24 * 400)), u'1 year and 1 month ago') + + def test_tag_exctrator(self): + sample = ( + "hello pta[tag] gog [[]] [[] sda ero[or]d [me =>>< sa]" + "[requires] [stale] [see<>=>] [see => http://url.com]" + "[requires => url] [lang => python] [just a tag]" + "[,d] [ => ULR ] [obsolete] [desc]]" + ) + from rhodecode.lib.helpers import desc_stylize + res = desc_stylize(sample) + self.assertTrue('
    tag
    ' in res) + self.assertTrue('
    obsolete
    ' in res) + self.assertTrue('
    stale
    ' in res) + self.assertTrue('
    python
    ' in res) + self.assertTrue('
    requires => url
    ' in res) + self.assertTrue('
    tag
    ' in res) + + def test_alternative_gravatar(self): + from rhodecode.lib.helpers import gravatar_url + _md5 = lambda s: hashlib.md5(s).hexdigest() + + def fake_conf(**kwargs): + from pylons import config + config['app_conf'] = {} + config['app_conf']['use_gravatar'] = True + config['app_conf'].update(kwargs) + return config + fake = fake_conf(alternative_gravatar_url='http://test.com/{email}') + with mock.patch('pylons.config', fake): + grav = gravatar_url(email_address='test@foo.com', size=24) + assert grav == 'http://test.com/test@foo.com' + + fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}') + with mock.patch('pylons.config', fake): + em = 'test@foo.com' + grav = gravatar_url(email_address=em, size=24) + assert grav == 'http://test.com/%s' % (_md5(em)) + + fake = fake_conf(alternative_gravatar_url='http://test.com/{md5email}/{size}') + with mock.patch('pylons.config', fake): + em = 'test@foo.com' + grav = gravatar_url(email_address=em, size=24) + assert grav == 'http://test.com/%s/%s' % (_md5(em), 24) diff --git a/rhodecode/tests/test_validators.py b/rhodecode/tests/test_validators.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/test_validators.py @@ -0,0 +1,246 @@ +# -*- coding: utf-8 -*- +import unittest +import formencode + +from rhodecode.tests import * + +from rhodecode.model import validators as v +from rhodecode.model.users_group import UsersGroupModel + +from rhodecode.model.meta import Session +from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.config.routing import ADMIN_PREFIX +from rhodecode.model.db import ChangesetStatus +from rhodecode.model.changeset_status import ChangesetStatusModel +from rhodecode.model.comment import ChangesetCommentsModel + + +class TestReposGroups(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_Message_extractor(self): + validator = v.ValidUsername() + self.assertRaises(formencode.Invalid, validator.to_python, 'default') + + class StateObj(object): + pass + + self.assertRaises(formencode.Invalid, + validator.to_python, 'default', StateObj) + + def test_ValidUsername(self): + validator = v.ValidUsername() + + self.assertRaises(formencode.Invalid, validator.to_python, 'default') + self.assertRaises(formencode.Invalid, validator.to_python, 'new_user') + self.assertRaises(formencode.Invalid, validator.to_python, '.,') + self.assertRaises(formencode.Invalid, validator.to_python, + TEST_USER_ADMIN_LOGIN) + self.assertEqual('test', validator.to_python('test')) + + validator = v.ValidUsername(edit=True, old_data={'user_id': 1}) + + def test_ValidRepoUser(self): + validator = v.ValidRepoUser() + self.assertRaises(formencode.Invalid, validator.to_python, 'nouser') + self.assertEqual(TEST_USER_ADMIN_LOGIN, + validator.to_python(TEST_USER_ADMIN_LOGIN)) + + def test_ValidUsersGroup(self): + validator = v.ValidUsersGroup() + self.assertRaises(formencode.Invalid, validator.to_python, 'default') + self.assertRaises(formencode.Invalid, validator.to_python, '.,') + + gr = UsersGroupModel().create('test') + gr2 = UsersGroupModel().create('tes2') + Session.commit() + self.assertRaises(formencode.Invalid, validator.to_python, 'test') + assert gr.users_group_id != None + validator = v.ValidUsersGroup(edit=True, + old_data={'users_group_id': + gr2.users_group_id}) + + self.assertRaises(formencode.Invalid, validator.to_python, 'test') + self.assertRaises(formencode.Invalid, validator.to_python, 'TesT') + self.assertRaises(formencode.Invalid, validator.to_python, 'TEST') + UsersGroupModel().delete(gr) + UsersGroupModel().delete(gr2) + Session.commit() + + def test_ValidReposGroup(self): + validator = v.ValidReposGroup() + model = ReposGroupModel() + self.assertRaises(formencode.Invalid, validator.to_python, + {'group_name': HG_REPO, }) + gr = model.create(group_name='test_gr', group_description='desc', + parent=None, + just_db=True) + self.assertRaises(formencode.Invalid, + validator.to_python, {'group_name': gr.group_name, }) + + validator = v.ValidReposGroup(edit=True, + old_data={'group_id': gr.group_id}) + self.assertRaises(formencode.Invalid, + validator.to_python, { + 'group_name': gr.group_name + 'n', + 'group_parent_id': gr.group_id + }) + model.delete(gr) + + def test_ValidPassword(self): + validator = v.ValidPassword() + self.assertEqual('lol', validator.to_python('lol')) + self.assertEqual(None, validator.to_python(None)) + self.assertRaises(formencode.Invalid, validator.to_python, 'ąćżź') + + def test_ValidPasswordsMatch(self): + validator = v.ValidPasswordsMatch() + self.assertRaises(formencode.Invalid, + validator.to_python, {'password': 'pass', + 'password_confirmation': 'pass2'}) + + self.assertRaises(formencode.Invalid, + validator.to_python, {'new_password': 'pass', + 'password_confirmation': 'pass2'}) + + self.assertEqual({'new_password': 'pass', + 'password_confirmation': 'pass'}, + validator.to_python({'new_password': 'pass', + 'password_confirmation': 'pass'})) + + self.assertEqual({'password': 'pass', + 'password_confirmation': 'pass'}, + validator.to_python({'password': 'pass', + 'password_confirmation': 'pass'})) + + def test_ValidAuth(self): + validator = v.ValidAuth() + valid_creds = { + 'username': TEST_USER_REGULAR2_LOGIN, + 'password': TEST_USER_REGULAR2_PASS, + } + invalid_creds = { + 'username': 'err', + 'password': 'err', + } + self.assertEqual(valid_creds, validator.to_python(valid_creds)) + self.assertRaises(formencode.Invalid, + validator.to_python, invalid_creds) + + def test_ValidAuthToken(self): + validator = v.ValidAuthToken() + # this is untestable without a threadlocal +# self.assertRaises(formencode.Invalid, +# validator.to_python, 'BadToken') + validator + + def test_ValidRepoName(self): + validator = v.ValidRepoName() + + self.assertRaises(formencode.Invalid, + validator.to_python, {'repo_name': ''}) + + self.assertRaises(formencode.Invalid, + validator.to_python, {'repo_name': HG_REPO}) + + gr = ReposGroupModel().create(group_name='group_test', + group_description='desc', + parent=None,) + self.assertRaises(formencode.Invalid, + validator.to_python, {'repo_name': gr.group_name}) + + #TODO: write an error case for that ie. create a repo withinh a group +# self.assertRaises(formencode.Invalid, +# validator.to_python, {'repo_name': 'some', +# 'repo_group': gr.group_id}) + + def test_ValidForkName(self): + # this uses ValidRepoName validator + assert True + + @parameterized.expand([ + ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'), + ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'), + ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'), + ('/]re po', 're-po')]) + def test_SlugifyName(self, name, expected): + validator = v.SlugifyName() + self.assertEqual(expected, validator.to_python(name)) + + def test_ValidCloneUri(self): + #TODO: write this one + pass + + def test_ValidForkType(self): + validator = v.ValidForkType(old_data={'repo_type': 'hg'}) + self.assertEqual('hg', validator.to_python('hg')) + self.assertRaises(formencode.Invalid, validator.to_python, 'git') + + def test_ValidPerms(self): + #TODO: write this one + pass + + def test_ValidSettings(self): + validator = v.ValidSettings() + self.assertEqual({'pass': 'pass'}, + validator.to_python(value={'user': 'test', + 'pass': 'pass'})) + + self.assertEqual({'user2': 'test', 'pass': 'pass'}, + validator.to_python(value={'user2': 'test', + 'pass': 'pass'})) + + def test_ValidPath(self): + validator = v.ValidPath() + self.assertEqual(TESTS_TMP_PATH, + validator.to_python(TESTS_TMP_PATH)) + self.assertRaises(formencode.Invalid, validator.to_python, + '/no_such_dir') + + def test_UniqSystemEmail(self): + validator = v.UniqSystemEmail(old_data={}) + + self.assertEqual('mail@python.org', + validator.to_python('MaiL@Python.org')) + + email = TEST_USER_REGULAR2_EMAIL + self.assertRaises(formencode.Invalid, validator.to_python, email) + + def test_ValidSystemEmail(self): + validator = v.ValidSystemEmail() + email = TEST_USER_REGULAR2_EMAIL + + self.assertEqual(email, validator.to_python(email)) + self.assertRaises(formencode.Invalid, validator.to_python, 'err') + + def test_LdapLibValidator(self): + validator = v.LdapLibValidator() + self.assertRaises(v.LdapImportError, validator.to_python, 'err') + + def test_AttrLoginValidator(self): + validator = v.AttrLoginValidator() + self.assertRaises(formencode.Invalid, validator.to_python, 123) + + def test_NotReviewedRevisions(self): + validator = v.NotReviewedRevisions() + rev = '0' * 40 + # add status for a rev, that should throw an error because it is already + # reviewed + new_status = ChangesetStatus() + new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN) + new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO) + new_status.status = ChangesetStatus.STATUS_APPROVED + new_status.comment = None + new_status.revision = rev + Session().add(new_status) + Session().commit() + try: + self.assertRaises(formencode.Invalid, validator.to_python, [rev]) + finally: + Session().delete(new_status) + Session().commit() diff --git a/rhodecode/tests/vcs/__init__.py b/rhodecode/tests/vcs/__init__.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/__init__.py @@ -0,0 +1,56 @@ +""" +Unit tests for vcs_ library. + +In order to run tests we need to prepare our environment first. Tests would be +run for each engine listed at ``conf.SCM_TESTS`` - keys are aliases from +``vcs.backends.BACKENDS``. + +For each SCM we run tests for, we need some repository. We would use +repositories location from system environment variables or test suite defaults +- see ``conf`` module for more detail. We simply try to check if repository at +certain location exists, if not we would try to fetch them. At ``test_vcs`` or +``test_common`` we run unit tests common for each repository type and for +example specific mercurial tests are located at ``test_hg`` module. + +Oh, and tests are run with ``unittest.collector`` wrapped by ``collector`` +function at ``tests/__init__.py``. + +.. _vcs: http://bitbucket.org/marcinkuzminski/vcs +.. _unittest: http://pypi.python.org/pypi/unittest + +""" +import os +from rhodecode.lib import vcs +from rhodecode.lib.vcs.utils.compat import unittest +from utils import VCSTestError, SCMFetcher +from rhodecode.tests import * + + +def setup_package(): + """ + Prepares whole package for tests which mainly means it would try to fetch + test repositories or use already existing ones. + """ + fetchers = { + 'hg': { + 'alias': 'hg', + 'test_repo_path': TEST_HG_REPO, + 'remote_repo': HG_REMOTE_REPO, + 'clone_cmd': 'hg clone', + }, + 'git': { + 'alias': 'git', + 'test_repo_path': TEST_GIT_REPO, + 'remote_repo': GIT_REMOTE_REPO, + 'clone_cmd': 'git clone --bare', + }, + } + try: + for scm, fetcher_info in fetchers.items(): + fetcher = SCMFetcher(**fetcher_info) + fetcher.setup() + except VCSTestError, err: + raise RuntimeError(str(err)) + +#start_dir = os.path.abspath(os.path.dirname(__file__)) +#unittest.defaultTestLoader.discover(start_dir) diff --git a/rhodecode/tests/vcs/aconfig b/rhodecode/tests/vcs/aconfig new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/aconfig @@ -0,0 +1,10 @@ +[user] +name = Foo Bar +email = foo.bar@example.com + +[ui] +username = Foo Bar foo.bar@example.com + +[universal] +foo = bar + diff --git a/rhodecode/tests/vcs/base.py b/rhodecode/tests/vcs/base.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/base.py @@ -0,0 +1,111 @@ +""" +Module providing backend independent mixin class. It requires that +InMemoryChangeset class is working properly at backend class. +""" +import os +from rhodecode.lib import vcs +import time +import shutil +import datetime +from rhodecode.lib.vcs.utils.compat import unittest + +from conf import SCM_TESTS, get_new_dir + +from rhodecode.lib.vcs.nodes import FileNode + + +class BackendTestMixin(object): + """ + This is a backend independent test case class which should be created + with ``type`` method. + + It is required to set following attributes at subclass: + + - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``) + - ``repo_path``: path to the repository which would be created for set of + tests + - ``recreate_repo_per_test``: If set to ``False``, repo would NOT be created + before every single test. Defaults to ``True``. + """ + recreate_repo_per_test = True + + @classmethod + def get_backend(cls): + return vcs.get_backend(cls.backend_alias) + + @classmethod + def _get_commits(cls): + commits = [ + { + 'message': u'Initial commit', + 'author': u'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('foobar', content='Foobar'), + FileNode('foobar2', content='Foobar II'), + FileNode('foo/bar/baz', content='baz here!'), + ], + }, + { + 'message': u'Changes...', + 'author': u'Jane Doe ', + 'date': datetime.datetime(2010, 1, 1, 21), + 'added': [ + FileNode('some/new.txt', content='news...'), + ], + 'changed': [ + FileNode('foobar', 'Foobar I'), + ], + 'removed': [], + }, + ] + return commits + + @classmethod + def setUpClass(cls): + Backend = cls.get_backend() + cls.backend_class = Backend + cls.repo_path = get_new_dir(str(time.time())) + cls.repo = Backend(cls.repo_path, create=True) + cls.imc = cls.repo.in_memory_changeset + + for commit in cls._get_commits(): + for node in commit.get('added', []): + cls.imc.add(FileNode(node.path, content=node.content)) + for node in commit.get('changed', []): + cls.imc.change(FileNode(node.path, content=node.content)) + for node in commit.get('removed', []): + cls.imc.remove(FileNode(node.path)) + + cls.tip = cls.imc.commit(message=unicode(commit['message']), + author=unicode(commit['author']), + date=commit['date']) + + @classmethod + def tearDownClass(cls): + if not getattr(cls, 'recreate_repo_per_test', False) and \ + 'VCS_REMOVE_TEST_DIRS' in os.environ: + shutil.rmtree(cls.repo_path) + + def setUp(self): + if getattr(self, 'recreate_repo_per_test', False): + self.__class__.setUpClass() + + def tearDown(self): + if getattr(self, 'recreate_repo_per_test', False) and \ + 'VCS_REMOVE_TEST_DIRS' in os.environ: + shutil.rmtree(self.repo_path) + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s base backend test' % alias).title().split()) + bases = (BackendTestMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/conf.py b/rhodecode/tests/vcs/conf.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/conf.py @@ -0,0 +1,62 @@ +""" +Unit tests configuration module for vcs. +""" + +import os +import time +import hashlib +import tempfile +import datetime +from rhodecode.tests import * +from utils import get_normalized_path +from os.path import join as jn + +TEST_TMP_PATH = TESTS_TMP_PATH +#__all__ = ( +# 'TEST_HG_REPO', 'TEST_GIT_REPO', 'HG_REMOTE_REPO', 'GIT_REMOTE_REPO', +# 'SCM_TESTS', +#) +# +#SCM_TESTS = ['hg', 'git'] +#uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple()))) +# +THIS = os.path.abspath(os.path.dirname(__file__)) +# +#GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git' +# +#TEST_TMP_PATH = os.environ.get('VCS_TEST_ROOT', '/tmp') +#TEST_GIT_REPO = os.environ.get('VCS_TEST_GIT_REPO', +# jn(TEST_TMP_PATH, 'vcs-git')) +#TEST_GIT_REPO_CLONE = os.environ.get('VCS_TEST_GIT_REPO_CLONE', +# jn(TEST_TMP_PATH, 'vcsgitclone%s' % uniq_suffix)) +#TEST_GIT_REPO_PULL = os.environ.get('VCS_TEST_GIT_REPO_PULL', +# jn(TEST_TMP_PATH, 'vcsgitpull%s' % uniq_suffix)) +# +#HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs' +#TEST_HG_REPO = os.environ.get('VCS_TEST_HG_REPO', +# jn(TEST_TMP_PATH, 'vcs-hg')) +#TEST_HG_REPO_CLONE = os.environ.get('VCS_TEST_HG_REPO_CLONE', +# jn(TEST_TMP_PATH, 'vcshgclone%s' % uniq_suffix)) +#TEST_HG_REPO_PULL = os.environ.get('VCS_TEST_HG_REPO_PULL', +# jn(TEST_TMP_PATH, 'vcshgpull%s' % uniq_suffix)) +# +#TEST_DIR = os.environ.get('VCS_TEST_ROOT', tempfile.gettempdir()) +#TEST_REPO_PREFIX = 'vcs-test' +# +# +#def get_new_dir(title): +# """ +# Returns always new directory path. +# """ +# name = TEST_REPO_PREFIX +# if title: +# name = '-'.join((name, title)) +# hex = hashlib.sha1(str(time.time())).hexdigest() +# name = '-'.join((name, hex)) +# path = os.path.join(TEST_DIR, name) +# return get_normalized_path(path) + +PACKAGE_DIR = os.path.abspath(os.path.join( + os.path.dirname(__file__), '..')) + +TEST_USER_CONFIG_FILE = jn(THIS, 'aconfig') diff --git a/rhodecode/tests/vcs/test_archives.py b/rhodecode/tests/vcs/test_archives.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_archives.py @@ -0,0 +1,108 @@ +from __future__ import with_statement + +import os +import tarfile +import zipfile +import datetime +import tempfile +import StringIO +from base import BackendTestMixin +from conf import SCM_TESTS +from rhodecode.lib.vcs.exceptions import VCSError +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.utils.compat import unittest + + +class ArchivesTestCaseMixin(BackendTestMixin): + + @classmethod + def _get_commits(cls): + start_date = datetime.datetime(2010, 1, 1, 20) + for x in xrange(5): + yield { + 'message': 'Commit %d' % x, + 'author': 'Joe Doe ', + 'date': start_date + datetime.timedelta(hours=12 * x), + 'added': [ + FileNode('%d/file_%d.txt' % (x, x), + content='Foobar %d' % x), + ], + } + + def test_archive_zip(self): + path = tempfile.mkstemp()[1] + with open(path, 'wb') as f: + self.tip.fill_archive(stream=f, kind='zip', prefix='repo') + out = zipfile.ZipFile(path) + + for x in xrange(5): + node_path = '%d/file_%d.txt' % (x, x) + decompressed = StringIO.StringIO() + decompressed.write(out.read('repo/' + node_path)) + self.assertEqual( + decompressed.getvalue(), + self.tip.get_node(node_path).content) + + def test_archive_tgz(self): + path = tempfile.mkstemp()[1] + with open(path, 'wb') as f: + self.tip.fill_archive(stream=f, kind='tgz', prefix='repo') + outdir = tempfile.mkdtemp() + + outfile = tarfile.open(path, 'r|gz') + outfile.extractall(outdir) + + for x in xrange(5): + node_path = '%d/file_%d.txt' % (x, x) + self.assertEqual( + open(os.path.join(outdir, 'repo/' + node_path)).read(), + self.tip.get_node(node_path).content) + + def test_archive_tbz2(self): + path = tempfile.mkstemp()[1] + with open(path, 'w+b') as f: + self.tip.fill_archive(stream=f, kind='tbz2', prefix='repo') + outdir = tempfile.mkdtemp() + + outfile = tarfile.open(path, 'r|bz2') + outfile.extractall(outdir) + + for x in xrange(5): + node_path = '%d/file_%d.txt' % (x, x) + self.assertEqual( + open(os.path.join(outdir, 'repo/' + node_path)).read(), + self.tip.get_node(node_path).content) + + def test_archive_default_stream(self): + tmppath = tempfile.mkstemp()[1] + with open(tmppath, 'w') as stream: + self.tip.fill_archive(stream=stream) + mystream = StringIO.StringIO() + self.tip.fill_archive(stream=mystream) + mystream.seek(0) + with open(tmppath, 'r') as f: + self.assertEqual(f.read(), mystream.read()) + + def test_archive_wrong_kind(self): + with self.assertRaises(VCSError): + self.tip.fill_archive(kind='wrong kind') + + def test_archive_empty_prefix(self): + with self.assertRaises(VCSError): + self.tip.fill_archive(prefix='') + + def test_archive_prefix_with_leading_slash(self): + with self.assertRaises(VCSError): + self.tip.fill_archive(prefix='/any') + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s archive test' % alias).title().split()) + bases = (ArchivesTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_branches.py b/rhodecode/tests/vcs/test_branches.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_branches.py @@ -0,0 +1,118 @@ +from __future__ import with_statement + +from rhodecode.lib import vcs +import datetime +from rhodecode.lib.vcs.utils.compat import unittest + +from base import BackendTestMixin +from conf import SCM_TESTS + +from rhodecode.lib.vcs.nodes import FileNode + + +class BranchesTestCaseMixin(BackendTestMixin): + + @classmethod + def _get_commits(cls): + commits = [ + { + 'message': 'Initial commit', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('foobar', content='Foobar'), + FileNode('foobar2', content='Foobar II'), + FileNode('foo/bar/baz', content='baz here!'), + ], + }, + { + 'message': 'Changes...', + 'author': 'Jane Doe ', + 'date': datetime.datetime(2010, 1, 1, 21), + 'added': [ + FileNode('some/new.txt', content='news...'), + ], + 'changed': [ + FileNode('foobar', 'Foobar I'), + ], + 'removed': [], + }, + ] + return commits + + def test_simple(self): + tip = self.repo.get_changeset() + self.assertEqual(tip.date, datetime.datetime(2010, 1, 1, 21)) + + def test_new_branch(self): + # This check must not be removed to ensure the 'branches' LazyProperty + # gets hit *before* the new 'foobar' branch got created: + self.assertFalse('foobar' in self.repo.branches) + self.imc.add(vcs.nodes.FileNode('docs/index.txt', + content='Documentation\n')) + foobar_tip = self.imc.commit( + message=u'New branch: foobar', + author=u'joe', + branch='foobar', + ) + self.assertTrue('foobar' in self.repo.branches) + self.assertEqual(foobar_tip.branch, 'foobar') + + def test_new_head(self): + tip = self.repo.get_changeset() + self.imc.add(vcs.nodes.FileNode('docs/index.txt', + content='Documentation\n')) + foobar_tip = self.imc.commit( + message=u'New branch: foobar', + author=u'joe', + branch='foobar', + parents=[tip], + ) + self.imc.change(vcs.nodes.FileNode('docs/index.txt', + content='Documentation\nand more...\n')) + newtip = self.imc.commit( + message=u'At default branch', + author=u'joe', + branch=foobar_tip.branch, + parents=[foobar_tip], + ) + + newest_tip = self.imc.commit( + message=u'Merged with %s' % foobar_tip.raw_id, + author=u'joe', + branch=self.backend_class.DEFAULT_BRANCH_NAME, + parents=[newtip, foobar_tip], + ) + + self.assertEqual(newest_tip.branch, + self.backend_class.DEFAULT_BRANCH_NAME) + + def test_branch_with_slash_in_name(self): + self.imc.add(vcs.nodes.FileNode('extrafile', content='Some data\n')) + self.imc.commit(u'Branch with a slash!', author=u'joe', + branch='issue/123') + self.assertTrue('issue/123' in self.repo.branches) + + def test_branch_with_slash_in_name_and_similar_without(self): + self.imc.add(vcs.nodes.FileNode('extrafile', content='Some data\n')) + self.imc.commit(u'Branch with a slash!', author=u'joe', + branch='issue/123') + self.imc.add(vcs.nodes.FileNode('extrafile II', content='Some data\n')) + self.imc.commit(u'Branch without a slash...', author=u'joe', + branch='123') + self.assertIn('issue/123', self.repo.branches) + self.assertIn('123', self.repo.branches) + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s branches test' % alias).title().split()) + bases = (BranchesTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_changesets.py b/rhodecode/tests/vcs/test_changesets.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_changesets.py @@ -0,0 +1,344 @@ +from __future__ import with_statement + +from rhodecode.lib import vcs +import datetime +from base import BackendTestMixin +from conf import SCM_TESTS +from rhodecode.lib.vcs.backends.base import BaseChangeset +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError +from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError +from rhodecode.lib.vcs.exceptions import RepositoryError +from rhodecode.lib.vcs.utils.compat import unittest + + +class TestBaseChangeset(unittest.TestCase): + + def test_as_dict(self): + changeset = BaseChangeset() + changeset.id = 'ID' + changeset.raw_id = 'RAW_ID' + changeset.short_id = 'SHORT_ID' + changeset.revision = 1009 + changeset.date = datetime.datetime(2011, 1, 30, 1, 45) + changeset.message = 'Message of a commit' + changeset.author = 'Joe Doe ' + changeset.added = [FileNode('foo/bar/baz'), FileNode('foobar')] + changeset.changed = [] + changeset.removed = [] + self.assertEqual(changeset.as_dict(), { + 'id': 'ID', + 'raw_id': 'RAW_ID', + 'short_id': 'SHORT_ID', + 'revision': 1009, + 'date': datetime.datetime(2011, 1, 30, 1, 45), + 'message': 'Message of a commit', + 'author': { + 'name': 'Joe Doe', + 'email': 'joe.doe@example.com', + }, + 'added': ['foo/bar/baz', 'foobar'], + 'changed': [], + 'removed': [], + }) + +class ChangesetsWithCommitsTestCaseixin(BackendTestMixin): + recreate_repo_per_test = True + + @classmethod + def _get_commits(cls): + start_date = datetime.datetime(2010, 1, 1, 20) + for x in xrange(5): + yield { + 'message': 'Commit %d' % x, + 'author': 'Joe Doe ', + 'date': start_date + datetime.timedelta(hours=12 * x), + 'added': [ + FileNode('file_%d.txt' % x, content='Foobar %d' % x), + ], + } + + def test_new_branch(self): + self.imc.add(vcs.nodes.FileNode('docs/index.txt', + content='Documentation\n')) + foobar_tip = self.imc.commit( + message=u'New branch: foobar', + author=u'joe', + branch='foobar', + ) + self.assertTrue('foobar' in self.repo.branches) + self.assertEqual(foobar_tip.branch, 'foobar') + # 'foobar' should be the only branch that contains the new commit + self.assertNotEqual(*self.repo.branches.values()) + + def test_new_head_in_default_branch(self): + tip = self.repo.get_changeset() + self.imc.add(vcs.nodes.FileNode('docs/index.txt', + content='Documentation\n')) + foobar_tip = self.imc.commit( + message=u'New branch: foobar', + author=u'joe', + branch='foobar', + parents=[tip], + ) + self.imc.change(vcs.nodes.FileNode('docs/index.txt', + content='Documentation\nand more...\n')) + newtip = self.imc.commit( + message=u'At default branch', + author=u'joe', + branch=foobar_tip.branch, + parents=[foobar_tip], + ) + + newest_tip = self.imc.commit( + message=u'Merged with %s' % foobar_tip.raw_id, + author=u'joe', + branch=self.backend_class.DEFAULT_BRANCH_NAME, + parents=[newtip, foobar_tip], + ) + + self.assertEqual(newest_tip.branch, + self.backend_class.DEFAULT_BRANCH_NAME) + + def test_get_changesets_respects_branch_name(self): + tip = self.repo.get_changeset() + self.imc.add(vcs.nodes.FileNode('docs/index.txt', + content='Documentation\n')) + doc_changeset = self.imc.commit( + message=u'New branch: docs', + author=u'joe', + branch='docs', + ) + self.imc.add(vcs.nodes.FileNode('newfile', content='')) + self.imc.commit( + message=u'Back in default branch', + author=u'joe', + parents=[tip], + ) + default_branch_changesets = self.repo.get_changesets( + branch_name=self.repo.DEFAULT_BRANCH_NAME) + self.assertNotIn(doc_changeset, default_branch_changesets) + + def test_get_changeset_by_branch(self): + for branch, sha in self.repo.branches.iteritems(): + self.assertEqual(sha, self.repo.get_changeset(branch).raw_id) + + def test_get_changeset_by_tag(self): + for tag, sha in self.repo.tags.iteritems(): + self.assertEqual(sha, self.repo.get_changeset(tag).raw_id) + + +class ChangesetsTestCaseMixin(BackendTestMixin): + recreate_repo_per_test = False + + @classmethod + def _get_commits(cls): + start_date = datetime.datetime(2010, 1, 1, 20) + for x in xrange(5): + yield { + 'message': u'Commit %d' % x, + 'author': u'Joe Doe ', + 'date': start_date + datetime.timedelta(hours=12 * x), + 'added': [ + FileNode('file_%d.txt' % x, content='Foobar %d' % x), + ], + } + + def test_simple(self): + tip = self.repo.get_changeset() + self.assertEqual(tip.date, datetime.datetime(2010, 1, 3, 20)) + + def test_get_changesets_is_ordered_by_date(self): + changesets = list(self.repo.get_changesets()) + ordered_by_date = sorted(changesets, + key=lambda cs: cs.date) + self.assertItemsEqual(changesets, ordered_by_date) + + def test_get_changesets_respects_start(self): + second_id = self.repo.revisions[1] + changesets = list(self.repo.get_changesets(start=second_id)) + self.assertEqual(len(changesets), 4) + + def test_get_changesets_numerical_id_respects_start(self): + second_id = 1 + changesets = list(self.repo.get_changesets(start=second_id)) + self.assertEqual(len(changesets), 4) + + def test_get_changesets_includes_start_changeset(self): + second_id = self.repo.revisions[1] + changesets = list(self.repo.get_changesets(start=second_id)) + self.assertEqual(changesets[0].raw_id, second_id) + + def test_get_changesets_respects_end(self): + second_id = self.repo.revisions[1] + changesets = list(self.repo.get_changesets(end=second_id)) + self.assertEqual(changesets[-1].raw_id, second_id) + self.assertEqual(len(changesets), 2) + + def test_get_changesets_numerical_id_respects_end(self): + second_id = 1 + changesets = list(self.repo.get_changesets(end=second_id)) + self.assertEqual(changesets.index(changesets[-1]), second_id) + self.assertEqual(len(changesets), 2) + + def test_get_changesets_respects_both_start_and_end(self): + second_id = self.repo.revisions[1] + third_id = self.repo.revisions[2] + changesets = list(self.repo.get_changesets(start=second_id, + end=third_id)) + self.assertEqual(len(changesets), 2) + + def test_get_changesets_numerical_id_respects_both_start_and_end(self): + changesets = list(self.repo.get_changesets(start=2, end=3)) + self.assertEqual(len(changesets), 2) + + def test_get_changesets_includes_end_changeset(self): + second_id = self.repo.revisions[1] + changesets = list(self.repo.get_changesets(end=second_id)) + self.assertEqual(changesets[-1].raw_id, second_id) + + def test_get_changesets_respects_start_date(self): + start_date = datetime.datetime(2010, 2, 1) + for cs in self.repo.get_changesets(start_date=start_date): + self.assertGreaterEqual(cs.date, start_date) + + def test_get_changesets_respects_end_date(self): + end_date = datetime.datetime(2010, 2, 1) + for cs in self.repo.get_changesets(end_date=end_date): + self.assertLessEqual(cs.date, end_date) + + def test_get_changesets_respects_reverse(self): + changesets_id_list = [cs.raw_id for cs in + self.repo.get_changesets(reverse=True)] + self.assertItemsEqual(changesets_id_list, reversed(self.repo.revisions)) + + def test_get_filenodes_generator(self): + tip = self.repo.get_changeset() + filepaths = [node.path for node in tip.get_filenodes_generator()] + self.assertItemsEqual(filepaths, ['file_%d.txt' % x for x in xrange(5)]) + + def test_size(self): + tip = self.repo.get_changeset() + size = 5 * len('Foobar N') # Size of 5 files + self.assertEqual(tip.size, size) + + def test_author(self): + tip = self.repo.get_changeset() + self.assertEqual(tip.author, u'Joe Doe ') + + def test_author_name(self): + tip = self.repo.get_changeset() + self.assertEqual(tip.author_name, u'Joe Doe') + + def test_author_email(self): + tip = self.repo.get_changeset() + self.assertEqual(tip.author_email, u'joe.doe@example.com') + + def test_get_changesets_raise_changesetdoesnotexist_for_wrong_start(self): + with self.assertRaises(ChangesetDoesNotExistError): + list(self.repo.get_changesets(start='foobar')) + + def test_get_changesets_raise_changesetdoesnotexist_for_wrong_end(self): + with self.assertRaises(ChangesetDoesNotExistError): + list(self.repo.get_changesets(end='foobar')) + + def test_get_changesets_raise_branchdoesnotexist_for_wrong_branch_name(self): + with self.assertRaises(BranchDoesNotExistError): + list(self.repo.get_changesets(branch_name='foobar')) + + def test_get_changesets_raise_repositoryerror_for_wrong_start_end(self): + start = self.repo.revisions[-1] + end = self.repo.revisions[0] + with self.assertRaises(RepositoryError): + list(self.repo.get_changesets(start=start, end=end)) + + def test_get_changesets_numerical_id_reversed(self): + with self.assertRaises(RepositoryError): + [x for x in self.repo.get_changesets(start=3, end=2)] + + def test_get_changesets_numerical_id_respects_both_start_and_end_last(self): + with self.assertRaises(RepositoryError): + last = len(self.repo.revisions) + list(self.repo.get_changesets(start=last-1, end=last-2)) + + def test_get_changesets_numerical_id_last_zero_error(self): + with self.assertRaises(RepositoryError): + last = len(self.repo.revisions) + list(self.repo.get_changesets(start=last-1, end=0)) + + +class ChangesetsChangesTestCaseMixin(BackendTestMixin): + recreate_repo_per_test = False + + @classmethod + def _get_commits(cls): + return [ + { + 'message': u'Initial', + 'author': u'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('foo/bar', content='foo'), + FileNode('foobar', content='foo'), + FileNode('qwe', content='foo'), + ], + }, + { + 'message': u'Massive changes', + 'author': u'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 22), + 'added': [FileNode('fallout', content='War never changes')], + 'changed': [ + FileNode('foo/bar', content='baz'), + FileNode('foobar', content='baz'), + ], + 'removed': [FileNode('qwe')], + }, + ] + + def test_initial_commit(self): + changeset = self.repo.get_changeset(0) + self.assertItemsEqual(changeset.added, [ + changeset.get_node('foo/bar'), + changeset.get_node('foobar'), + changeset.get_node('qwe'), + ]) + self.assertItemsEqual(changeset.changed, []) + self.assertItemsEqual(changeset.removed, []) + + def test_head_added(self): + changeset = self.repo.get_changeset() + self.assertItemsEqual(changeset.added, [ + changeset.get_node('fallout'), + ]) + self.assertItemsEqual(changeset.changed, [ + changeset.get_node('foo/bar'), + changeset.get_node('foobar'), + ]) + self.assertEqual(len(changeset.removed), 1) + self.assertEqual(list(changeset.removed)[0].path, 'qwe') + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + # tests with additional commits + cls_name = ''.join(('%s changesets with commits test' % alias).title().split()) + bases = (ChangesetsWithCommitsTestCaseixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + # tests without additional commits + cls_name = ''.join(('%s changesets test' % alias).title().split()) + bases = (ChangesetsTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + # tests changes + cls_name = ''.join(('%s changesets changes test' % alias).title().split()) + bases = (ChangesetsChangesTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_filenodes_unicode_path.py b/rhodecode/tests/vcs/test_filenodes_unicode_path.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_filenodes_unicode_path.py @@ -0,0 +1,49 @@ +# encoding: utf8 + +from __future__ import with_statement + +import datetime +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.utils.compat import unittest +from test_inmemchangesets import BackendBaseTestCase +from conf import SCM_TESTS + + +class FileNodeUnicodePathTestsMixin(object): + + fname = 'ąśðąęłąć.txt' + ufname = (fname).decode('utf-8') + + def get_commits(self): + self.nodes = [ + FileNode(self.fname, content='Foobar'), + ] + + commits = [ + { + 'message': 'Initial commit', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': self.nodes, + }, + ] + return commits + + def test_filenode_path(self): + node = self.tip.get_node(self.fname) + unode = self.tip.get_node(self.ufname) + self.assertEqual(node, unode) + + +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s file node unicode path test' % alias).title() + .split()) + bases = (FileNodeUnicodePathTestsMixin, BackendBaseTestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_getitem.py b/rhodecode/tests/vcs/test_getitem.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_getitem.py @@ -0,0 +1,44 @@ +from __future__ import with_statement + +import datetime +from base import BackendTestMixin +from conf import SCM_TESTS +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.utils.compat import unittest + + +class GetitemTestCaseMixin(BackendTestMixin): + + @classmethod + def _get_commits(cls): + start_date = datetime.datetime(2010, 1, 1, 20) + for x in xrange(5): + yield { + 'message': 'Commit %d' % x, + 'author': 'Joe Doe ', + 'date': start_date + datetime.timedelta(hours=12 * x), + 'added': [ + FileNode('file_%d.txt' % x, content='Foobar %d' % x), + ], + } + + def test__getitem__last_item_is_tip(self): + self.assertEqual(self.repo[-1], self.repo.get_changeset()) + + def test__getitem__returns_correct_items(self): + changesets = [self.repo[x] for x in xrange(len(self.repo.revisions))] + self.assertEqual(changesets, list(self.repo.get_changesets())) + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s getitem test' % alias).title().split()) + bases = (GetitemTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_getslice.py b/rhodecode/tests/vcs/test_getslice.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_getslice.py @@ -0,0 +1,56 @@ +from __future__ import with_statement + +import datetime +from base import BackendTestMixin +from conf import SCM_TESTS +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.utils.compat import unittest + + +class GetsliceTestCaseMixin(BackendTestMixin): + + @classmethod + def _get_commits(cls): + start_date = datetime.datetime(2010, 1, 1, 20) + for x in xrange(5): + yield { + 'message': 'Commit %d' % x, + 'author': 'Joe Doe ', + 'date': start_date + datetime.timedelta(hours=12 * x), + 'added': [ + FileNode('file_%d.txt' % x, content='Foobar %d' % x), + ], + } + + def test__getslice__last_item_is_tip(self): + self.assertEqual(list(self.repo[-1:])[0], self.repo.get_changeset()) + + def test__getslice__respects_start_index(self): + self.assertEqual(list(self.repo[2:]), + [self.repo.get_changeset(rev) for rev in self.repo.revisions[2:]]) + + def test__getslice__respects_negative_start_index(self): + self.assertEqual(list(self.repo[-2:]), + [self.repo.get_changeset(rev) for rev in self.repo.revisions[-2:]]) + + def test__getslice__respects_end_index(self): + self.assertEqual(list(self.repo[:2]), + [self.repo.get_changeset(rev) for rev in self.repo.revisions[:2]]) + + def test__getslice__respects_negative_end_index(self): + self.assertEqual(list(self.repo[:-2]), + [self.repo.get_changeset(rev) for rev in self.repo.revisions[:-2]]) + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s getslice test' % alias).title().split()) + bases = (GetsliceTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_git.py b/rhodecode/tests/vcs/test_git.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_git.py @@ -0,0 +1,702 @@ +from __future__ import with_statement + +import os +import mock +import datetime +from rhodecode.lib.vcs.backends.git import GitRepository, GitChangeset +from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError +from rhodecode.lib.vcs.nodes import NodeKind, FileNode, DirNode, NodeState +from rhodecode.lib.vcs.utils.compat import unittest +from rhodecode.tests.vcs.base import BackendTestMixin +from conf import TEST_GIT_REPO, TEST_GIT_REPO_CLONE, get_new_dir + + +class GitRepositoryTest(unittest.TestCase): + + def __check_for_existing_repo(self): + if os.path.exists(TEST_GIT_REPO_CLONE): + self.fail('Cannot test git clone repo as location %s already ' + 'exists. You should manually remove it first.' + % TEST_GIT_REPO_CLONE) + + def setUp(self): + self.repo = GitRepository(TEST_GIT_REPO) + + def test_wrong_repo_path(self): + wrong_repo_path = '/tmp/errorrepo' + self.assertRaises(RepositoryError, GitRepository, wrong_repo_path) + + def test_repo_clone(self): + self.__check_for_existing_repo() + repo = GitRepository(TEST_GIT_REPO) + repo_clone = GitRepository(TEST_GIT_REPO_CLONE, + src_url=TEST_GIT_REPO, create=True, update_after_clone=True) + self.assertEqual(len(repo.revisions), len(repo_clone.revisions)) + # Checking hashes of changesets should be enough + for changeset in repo.get_changesets(): + raw_id = changeset.raw_id + self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id) + + def test_repo_clone_without_create(self): + self.assertRaises(RepositoryError, GitRepository, + TEST_GIT_REPO_CLONE + '_wo_create', src_url=TEST_GIT_REPO) + + def test_repo_clone_with_update(self): + repo = GitRepository(TEST_GIT_REPO) + clone_path = TEST_GIT_REPO_CLONE + '_with_update' + repo_clone = GitRepository(clone_path, + create=True, src_url=TEST_GIT_REPO, update_after_clone=True) + self.assertEqual(len(repo.revisions), len(repo_clone.revisions)) + + #check if current workdir was updated + fpath = os.path.join(clone_path, 'MANIFEST.in') + self.assertEqual(True, os.path.isfile(fpath), + 'Repo was cloned and updated but file %s could not be found' + % fpath) + + def test_repo_clone_without_update(self): + repo = GitRepository(TEST_GIT_REPO) + clone_path = TEST_GIT_REPO_CLONE + '_without_update' + repo_clone = GitRepository(clone_path, + create=True, src_url=TEST_GIT_REPO, update_after_clone=False) + self.assertEqual(len(repo.revisions), len(repo_clone.revisions)) + #check if current workdir was *NOT* updated + fpath = os.path.join(clone_path, 'MANIFEST.in') + # Make sure it's not bare repo + self.assertFalse(repo_clone._repo.bare) + self.assertEqual(False, os.path.isfile(fpath), + 'Repo was cloned and updated but file %s was found' + % fpath) + + def test_repo_clone_into_bare_repo(self): + repo = GitRepository(TEST_GIT_REPO) + clone_path = TEST_GIT_REPO_CLONE + '_bare.git' + repo_clone = GitRepository(clone_path, create=True, + src_url=repo.path, bare=True) + self.assertTrue(repo_clone._repo.bare) + + def test_create_repo_is_not_bare_by_default(self): + repo = GitRepository(get_new_dir('not-bare-by-default'), create=True) + self.assertFalse(repo._repo.bare) + + def test_create_bare_repo(self): + repo = GitRepository(get_new_dir('bare-repo'), create=True, bare=True) + self.assertTrue(repo._repo.bare) + + def test_revisions(self): + # there are 112 revisions (by now) + # so we can assume they would be available from now on + subset = set([ + 'c1214f7e79e02fc37156ff215cd71275450cffc3', + '38b5fe81f109cb111f549bfe9bb6b267e10bc557', + 'fa6600f6848800641328adbf7811fd2372c02ab2', + '102607b09cdd60e2793929c4f90478be29f85a17', + '49d3fd156b6f7db46313fac355dca1a0b94a0017', + '2d1028c054665b962fa3d307adfc923ddd528038', + 'd7e0d30fbcae12c90680eb095a4f5f02505ce501', + 'ff7ca51e58c505fec0dd2491de52c622bb7a806b', + 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f', + '8430a588b43b5d6da365400117c89400326e7992', + 'd955cd312c17b02143c04fa1099a352b04368118', + 'f67b87e5c629c2ee0ba58f85197e423ff28d735b', + 'add63e382e4aabc9e1afdc4bdc24506c269b7618', + 'f298fe1189f1b69779a4423f40b48edf92a703fc', + 'bd9b619eb41994cac43d67cf4ccc8399c1125808', + '6e125e7c890379446e98980d8ed60fba87d0f6d1', + 'd4a54db9f745dfeba6933bf5b1e79e15d0af20bd', + '0b05e4ed56c802098dfc813cbe779b2f49e92500', + '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e', + '45223f8f114c64bf4d6f853e3c35a369a6305520', + 'ca1eb7957a54bce53b12d1a51b13452f95bc7c7e', + 'f5ea29fc42ef67a2a5a7aecff10e1566699acd68', + '27d48942240f5b91dfda77accd2caac94708cc7d', + '622f0eb0bafd619d2560c26f80f09e3b0b0d78af', + 'e686b958768ee96af8029fe19c6050b1a8dd3b2b']) + self.assertTrue(subset.issubset(set(self.repo.revisions))) + + + + def test_slicing(self): + #4 1 5 10 95 + for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5), + (10, 20, 10), (5, 100, 95)]: + revs = list(self.repo[sfrom:sto]) + self.assertEqual(len(revs), size) + self.assertEqual(revs[0], self.repo.get_changeset(sfrom)) + self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1)) + + + def test_branches(self): + # TODO: Need more tests here + # Removed (those are 'remotes' branches for cloned repo) + #self.assertTrue('master' in self.repo.branches) + #self.assertTrue('gittree' in self.repo.branches) + #self.assertTrue('web-branch' in self.repo.branches) + for name, id in self.repo.branches.items(): + self.assertTrue(isinstance( + self.repo.get_changeset(id), GitChangeset)) + + def test_tags(self): + # TODO: Need more tests here + self.assertTrue('v0.1.1' in self.repo.tags) + self.assertTrue('v0.1.2' in self.repo.tags) + for name, id in self.repo.tags.items(): + self.assertTrue(isinstance( + self.repo.get_changeset(id), GitChangeset)) + + def _test_single_changeset_cache(self, revision): + chset = self.repo.get_changeset(revision) + self.assertTrue(revision in self.repo.changesets) + self.assertTrue(chset is self.repo.changesets[revision]) + + def test_initial_changeset(self): + id = self.repo.revisions[0] + init_chset = self.repo.get_changeset(id) + self.assertEqual(init_chset.message, 'initial import\n') + self.assertEqual(init_chset.author, + 'Marcin Kuzminski ') + for path in ('vcs/__init__.py', + 'vcs/backends/BaseRepository.py', + 'vcs/backends/__init__.py'): + self.assertTrue(isinstance(init_chset.get_node(path), FileNode)) + for path in ('', 'vcs', 'vcs/backends'): + self.assertTrue(isinstance(init_chset.get_node(path), DirNode)) + + self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar') + + node = init_chset.get_node('vcs/') + self.assertTrue(hasattr(node, 'kind')) + self.assertEqual(node.kind, NodeKind.DIR) + + node = init_chset.get_node('vcs') + self.assertTrue(hasattr(node, 'kind')) + self.assertEqual(node.kind, NodeKind.DIR) + + node = init_chset.get_node('vcs/__init__.py') + self.assertTrue(hasattr(node, 'kind')) + self.assertEqual(node.kind, NodeKind.FILE) + + def test_not_existing_changeset(self): + self.assertRaises(RepositoryError, self.repo.get_changeset, + 'f' * 40) + + def test_changeset10(self): + + chset10 = self.repo.get_changeset(self.repo.revisions[9]) + README = """=== +VCS +=== + +Various Version Control System management abstraction layer for Python. + +Introduction +------------ + +TODO: To be written... + +""" + node = chset10.get_node('README.rst') + self.assertEqual(node.kind, NodeKind.FILE) + self.assertEqual(node.content, README) + + +class GitChangesetTest(unittest.TestCase): + + def setUp(self): + self.repo = GitRepository(TEST_GIT_REPO) + + def test_default_changeset(self): + tip = self.repo.get_changeset() + self.assertEqual(tip, self.repo.get_changeset(None)) + self.assertEqual(tip, self.repo.get_changeset('tip')) + + def test_root_node(self): + tip = self.repo.get_changeset() + self.assertTrue(tip.root is tip.get_node('')) + + def test_lazy_fetch(self): + """ + Test if changeset's nodes expands and are cached as we walk through + the revision. This test is somewhat hard to write as order of tests + is a key here. Written by running command after command in a shell. + """ + hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc' + self.assertTrue(hex in self.repo.revisions) + chset = self.repo.get_changeset(hex) + self.assertTrue(len(chset.nodes) == 0) + root = chset.root + self.assertTrue(len(chset.nodes) == 1) + self.assertTrue(len(root.nodes) == 8) + # accessing root.nodes updates chset.nodes + self.assertTrue(len(chset.nodes) == 9) + + docs = root.get_node('docs') + # we haven't yet accessed anything new as docs dir was already cached + self.assertTrue(len(chset.nodes) == 9) + self.assertTrue(len(docs.nodes) == 8) + # accessing docs.nodes updates chset.nodes + self.assertTrue(len(chset.nodes) == 17) + + self.assertTrue(docs is chset.get_node('docs')) + self.assertTrue(docs is root.nodes[0]) + self.assertTrue(docs is root.dirs[0]) + self.assertTrue(docs is chset.get_node('docs')) + + def test_nodes_with_changeset(self): + hex = '2a13f185e4525f9d4b59882791a2d397b90d5ddc' + chset = self.repo.get_changeset(hex) + root = chset.root + docs = root.get_node('docs') + self.assertTrue(docs is chset.get_node('docs')) + api = docs.get_node('api') + self.assertTrue(api is chset.get_node('docs/api')) + index = api.get_node('index.rst') + self.assertTrue(index is chset.get_node('docs/api/index.rst')) + self.assertTrue(index is chset.get_node('docs')\ + .get_node('api')\ + .get_node('index.rst')) + + def test_branch_and_tags(self): + ''' + rev0 = self.repo.revisions[0] + chset0 = self.repo.get_changeset(rev0) + self.assertEqual(chset0.branch, 'master') + self.assertEqual(chset0.tags, []) + + rev10 = self.repo.revisions[10] + chset10 = self.repo.get_changeset(rev10) + self.assertEqual(chset10.branch, 'master') + self.assertEqual(chset10.tags, []) + + rev44 = self.repo.revisions[44] + chset44 = self.repo.get_changeset(rev44) + self.assertEqual(chset44.branch, 'web-branch') + + tip = self.repo.get_changeset('tip') + self.assertTrue('tip' in tip.tags) + ''' + # Those tests would fail - branches are now going + # to be changed at main API in order to support git backend + pass + + def _test_slices(self, limit, offset): + count = self.repo.count() + changesets = self.repo.get_changesets(limit=limit, offset=offset) + idx = 0 + for changeset in changesets: + rev = offset + idx + idx += 1 + rev_id = self.repo.revisions[rev] + if idx > limit: + self.fail("Exceeded limit already (getting revision %s, " + "there are %s total revisions, offset=%s, limit=%s)" + % (rev_id, count, offset, limit)) + self.assertEqual(changeset, self.repo.get_changeset(rev_id)) + result = list(self.repo.get_changesets(limit=limit, offset=offset)) + start = offset + end = limit and offset + limit or None + sliced = list(self.repo[start:end]) + self.failUnlessEqual(result, sliced, + msg="Comparison failed for limit=%s, offset=%s" + "(get_changeset returned: %s and sliced: %s" + % (limit, offset, result, sliced)) + + def _test_file_size(self, revision, path, size): + node = self.repo.get_changeset(revision).get_node(path) + self.assertTrue(node.is_file()) + self.assertEqual(node.size, size) + + def test_file_size(self): + to_check = ( + ('c1214f7e79e02fc37156ff215cd71275450cffc3', + 'vcs/backends/BaseRepository.py', 502), + ('d7e0d30fbcae12c90680eb095a4f5f02505ce501', + 'vcs/backends/hg.py', 854), + ('6e125e7c890379446e98980d8ed60fba87d0f6d1', + 'setup.py', 1068), + + ('d955cd312c17b02143c04fa1099a352b04368118', + 'vcs/backends/base.py', 2921), + ('ca1eb7957a54bce53b12d1a51b13452f95bc7c7e', + 'vcs/backends/base.py', 3936), + ('f50f42baeed5af6518ef4b0cb2f1423f3851a941', + 'vcs/backends/base.py', 6189), + ) + for revision, path, size in to_check: + self._test_file_size(revision, path, size) + + def test_file_history(self): + # we can only check if those revisions are present in the history + # as we cannot update this test every time file is changed + files = { + 'setup.py': [ + '54386793436c938cff89326944d4c2702340037d', + '51d254f0ecf5df2ce50c0b115741f4cf13985dab', + '998ed409c795fec2012b1c0ca054d99888b22090', + '5e0eb4c47f56564395f76333f319d26c79e2fb09', + '0115510b70c7229dbc5dc49036b32e7d91d23acd', + '7cb3fd1b6d8c20ba89e2264f1c8baebc8a52d36e', + '2a13f185e4525f9d4b59882791a2d397b90d5ddc', + '191caa5b2c81ed17c0794bf7bb9958f4dcb0b87e', + 'ff7ca51e58c505fec0dd2491de52c622bb7a806b', + ], + 'vcs/nodes.py': [ + '33fa3223355104431402a888fa77a4e9956feb3e', + 'fa014c12c26d10ba682fadb78f2a11c24c8118e1', + 'e686b958768ee96af8029fe19c6050b1a8dd3b2b', + 'ab5721ca0a081f26bf43d9051e615af2cc99952f', + 'c877b68d18e792a66b7f4c529ea02c8f80801542', + '4313566d2e417cb382948f8d9d7c765330356054', + '6c2303a793671e807d1cfc70134c9ca0767d98c2', + '54386793436c938cff89326944d4c2702340037d', + '54000345d2e78b03a99d561399e8e548de3f3203', + '1c6b3677b37ea064cb4b51714d8f7498f93f4b2b', + '2d03ca750a44440fb5ea8b751176d1f36f8e8f46', + '2a08b128c206db48c2f0b8f70df060e6db0ae4f8', + '30c26513ff1eb8e5ce0e1c6b477ee5dc50e2f34b', + 'ac71e9503c2ca95542839af0ce7b64011b72ea7c', + '12669288fd13adba2a9b7dd5b870cc23ffab92d2', + '5a0c84f3e6fe3473e4c8427199d5a6fc71a9b382', + '12f2f5e2b38e6ff3fbdb5d722efed9aa72ecb0d5', + '5eab1222a7cd4bfcbabc218ca6d04276d4e27378', + 'f50f42baeed5af6518ef4b0cb2f1423f3851a941', + 'd7e390a45f6aa96f04f5e7f583ad4f867431aa25', + 'f15c21f97864b4f071cddfbf2750ec2e23859414', + 'e906ef056cf539a4e4e5fc8003eaf7cf14dd8ade', + 'ea2b108b48aa8f8c9c4a941f66c1a03315ca1c3b', + '84dec09632a4458f79f50ddbbd155506c460b4f9', + '0115510b70c7229dbc5dc49036b32e7d91d23acd', + '2a13f185e4525f9d4b59882791a2d397b90d5ddc', + '3bf1c5868e570e39569d094f922d33ced2fa3b2b', + 'b8d04012574729d2c29886e53b1a43ef16dd00a1', + '6970b057cffe4aab0a792aa634c89f4bebf01441', + 'dd80b0f6cf5052f17cc738c2951c4f2070200d7f', + 'ff7ca51e58c505fec0dd2491de52c622bb7a806b', + ], + 'vcs/backends/git.py': [ + '4cf116ad5a457530381135e2f4c453e68a1b0105', + '9a751d84d8e9408e736329767387f41b36935153', + 'cb681fb539c3faaedbcdf5ca71ca413425c18f01', + '428f81bb652bcba8d631bce926e8834ff49bdcc6', + '180ab15aebf26f98f714d8c68715e0f05fa6e1c7', + '2b8e07312a2e89e92b90426ab97f349f4bce2a3a', + '50e08c506174d8645a4bb517dd122ac946a0f3bf', + '54000345d2e78b03a99d561399e8e548de3f3203', + ], + } + for path, revs in files.items(): + node = self.repo.get_changeset(revs[0]).get_node(path) + node_revs = [chset.raw_id for chset in node.history] + self.assertTrue(set(revs).issubset(set(node_revs)), + "We assumed that %s is subset of revisions for which file %s " + "has been changed, and history of that node returned: %s" + % (revs, path, node_revs)) + + def test_file_annotate(self): + files = { + 'vcs/backends/__init__.py': { + 'c1214f7e79e02fc37156ff215cd71275450cffc3': { + 'lines_no': 1, + 'changesets': [ + 'c1214f7e79e02fc37156ff215cd71275450cffc3', + ], + }, + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647': { + 'lines_no': 21, + 'changesets': [ + '49d3fd156b6f7db46313fac355dca1a0b94a0017', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + ], + }, + 'e29b67bd158580fc90fc5e9111240b90e6e86064': { + 'lines_no': 32, + 'changesets': [ + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '5eab1222a7cd4bfcbabc218ca6d04276d4e27378', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '54000345d2e78b03a99d561399e8e548de3f3203', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '78c3f0c23b7ee935ec276acb8b8212444c33c396', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '2a13f185e4525f9d4b59882791a2d397b90d5ddc', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '78c3f0c23b7ee935ec276acb8b8212444c33c396', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '992f38217b979d0b0987d0bae3cc26dac85d9b19', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + '16fba1ae9334d79b66d7afed2c2dfbfa2ae53647', + ], + }, + }, + } + + for fname, revision_dict in files.items(): + for rev, data in revision_dict.items(): + cs = self.repo.get_changeset(rev) + ann = cs.get_file_annotate(fname) + + l1 = [x[1].raw_id for x in ann] + l2 = files[fname][rev]['changesets'] + self.assertTrue(l1 == l2 , "The lists of revision for %s@rev %s" + "from annotation list should match each other, " + "got \n%s \nvs \n%s " % (fname, rev, l1, l2)) + + def test_files_state(self): + """ + Tests state of FileNodes. + """ + node = self.repo\ + .get_changeset('e6ea6d16e2f26250124a1f4b4fe37a912f9d86a0')\ + .get_node('vcs/utils/diffs.py') + self.assertTrue(node.state, NodeState.ADDED) + self.assertTrue(node.added) + self.assertFalse(node.changed) + self.assertFalse(node.not_changed) + self.assertFalse(node.removed) + + node = self.repo\ + .get_changeset('33fa3223355104431402a888fa77a4e9956feb3e')\ + .get_node('.hgignore') + self.assertTrue(node.state, NodeState.CHANGED) + self.assertFalse(node.added) + self.assertTrue(node.changed) + self.assertFalse(node.not_changed) + self.assertFalse(node.removed) + + node = self.repo\ + .get_changeset('e29b67bd158580fc90fc5e9111240b90e6e86064')\ + .get_node('setup.py') + self.assertTrue(node.state, NodeState.NOT_CHANGED) + self.assertFalse(node.added) + self.assertFalse(node.changed) + self.assertTrue(node.not_changed) + self.assertFalse(node.removed) + + # If node has REMOVED state then trying to fetch it would raise + # ChangesetError exception + chset = self.repo.get_changeset( + 'fa6600f6848800641328adbf7811fd2372c02ab2') + path = 'vcs/backends/BaseRepository.py' + self.assertRaises(NodeDoesNotExistError, chset.get_node, path) + # but it would be one of ``removed`` (changeset's attribute) + self.assertTrue(path in [rf.path for rf in chset.removed]) + + chset = self.repo.get_changeset( + '54386793436c938cff89326944d4c2702340037d') + changed = ['setup.py', 'tests/test_nodes.py', 'vcs/backends/hg.py', + 'vcs/nodes.py'] + self.assertEqual(set(changed), set([f.path for f in chset.changed])) + + def test_commit_message_is_unicode(self): + for cs in self.repo: + self.assertEqual(type(cs.message), unicode) + + def test_changeset_author_is_unicode(self): + for cs in self.repo: + self.assertEqual(type(cs.author), unicode) + + def test_repo_files_content_is_unicode(self): + changeset = self.repo.get_changeset() + for node in changeset.get_node('/'): + if node.is_file(): + self.assertEqual(type(node.content), unicode) + + def test_wrong_path(self): + # There is 'setup.py' in the root dir but not there: + path = 'foo/bar/setup.py' + tip = self.repo.get_changeset() + self.assertRaises(VCSError, tip.get_node, path) + + def test_author_email(self): + self.assertEqual('marcin@python-blog.com', + self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3')\ + .author_email) + self.assertEqual('lukasz.balcerzak@python-center.pl', + self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b')\ + .author_email) + self.assertEqual('none@none', + self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992')\ + .author_email) + + def test_author_username(self): + self.assertEqual('Marcin Kuzminski', + self.repo.get_changeset('c1214f7e79e02fc37156ff215cd71275450cffc3')\ + .author_name) + self.assertEqual('Lukasz Balcerzak', + self.repo.get_changeset('ff7ca51e58c505fec0dd2491de52c622bb7a806b')\ + .author_name) + self.assertEqual('marcink', + self.repo.get_changeset('8430a588b43b5d6da365400117c89400326e7992')\ + .author_name) + + +class GitSpecificTest(unittest.TestCase): + + def test_error_is_raised_for_added_if_diff_name_status_is_wrong(self): + repo = mock.MagicMock() + changeset = GitChangeset(repo, 'foobar') + changeset._diff_name_status = 'foobar' + with self.assertRaises(VCSError): + changeset.added + + def test_error_is_raised_for_changed_if_diff_name_status_is_wrong(self): + repo = mock.MagicMock() + changeset = GitChangeset(repo, 'foobar') + changeset._diff_name_status = 'foobar' + with self.assertRaises(VCSError): + changeset.added + + def test_error_is_raised_for_removed_if_diff_name_status_is_wrong(self): + repo = mock.MagicMock() + changeset = GitChangeset(repo, 'foobar') + changeset._diff_name_status = 'foobar' + with self.assertRaises(VCSError): + changeset.added + + +class GitSpecificWithRepoTest(BackendTestMixin, unittest.TestCase): + backend_alias = 'git' + + @classmethod + def _get_commits(cls): + return [ + { + 'message': 'Initial', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('foobar/static/js/admin/base.js', content='base'), + FileNode('foobar/static/admin', content='admin', + mode=0120000), # this is a link + FileNode('foo', content='foo'), + ], + }, + { + 'message': 'Second', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 22), + 'added': [ + FileNode('foo2', content='foo2'), + ], + }, + ] + + def test_paths_slow_traversing(self): + cs = self.repo.get_changeset() + self.assertEqual(cs.get_node('foobar').get_node('static').get_node('js') + .get_node('admin').get_node('base.js').content, 'base') + + def test_paths_fast_traversing(self): + cs = self.repo.get_changeset() + self.assertEqual(cs.get_node('foobar/static/js/admin/base.js').content, + 'base') + + def test_workdir_get_branch(self): + self.repo.run_git_command('checkout -b production') + # Regression test: one of following would fail if we don't check + # .git/HEAD file + self.repo.run_git_command('checkout production') + self.assertEqual(self.repo.workdir.get_branch(), 'production') + self.repo.run_git_command('checkout master') + self.assertEqual(self.repo.workdir.get_branch(), 'master') + + def test_get_diff_runs_git_command_with_hashes(self): + self.repo.run_git_command = mock.Mock(return_value=['', '']) + self.repo.get_diff(0, 1) + self.repo.run_git_command.assert_called_once_with('diff -U%s %s %s' % + (3, self.repo._get_revision(0), self.repo._get_revision(1))) + + def test_get_diff_runs_git_command_with_str_hashes(self): + self.repo.run_git_command = mock.Mock(return_value=['', '']) + self.repo.get_diff(self.repo.EMPTY_CHANGESET, 1) + self.repo.run_git_command.assert_called_once_with('show -U%s %s' % + (3, self.repo._get_revision(1))) + + def test_get_diff_runs_git_command_with_path_if_its_given(self): + self.repo.run_git_command = mock.Mock(return_value=['', '']) + self.repo.get_diff(0, 1, 'foo') + self.repo.run_git_command.assert_called_once_with('diff -U%s %s %s -- "foo"' + % (3, self.repo._get_revision(0), self.repo._get_revision(1))) + + +class GitRegressionTest(BackendTestMixin, unittest.TestCase): + backend_alias = 'git' + + @classmethod + def _get_commits(cls): + return [ + { + 'message': 'Initial', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('bot/__init__.py', content='base'), + FileNode('bot/templates/404.html', content='base'), + FileNode('bot/templates/500.html', content='base'), + ], + }, + { + 'message': 'Second', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 22), + 'added': [ + FileNode('bot/build/migrations/1.py', content='foo2'), + FileNode('bot/build/migrations/2.py', content='foo2'), + FileNode('bot/build/static/templates/f.html', content='foo2'), + FileNode('bot/build/static/templates/f1.html', content='foo2'), + FileNode('bot/build/templates/err.html', content='foo2'), + FileNode('bot/build/templates/err2.html', content='foo2'), + ], + }, + ] + + def test_similar_paths(self): + cs = self.repo.get_changeset() + paths = lambda *n:[x.path for x in n] + self.assertEqual(paths(*cs.get_nodes('bot')), ['bot/build', 'bot/templates', 'bot/__init__.py']) + self.assertEqual(paths(*cs.get_nodes('bot/build')), ['bot/build/migrations', 'bot/build/static', 'bot/build/templates']) + self.assertEqual(paths(*cs.get_nodes('bot/build/static')), ['bot/build/static/templates']) + # this get_nodes below causes troubles ! + self.assertEqual(paths(*cs.get_nodes('bot/build/static/templates')), ['bot/build/static/templates/f.html', 'bot/build/static/templates/f1.html']) + self.assertEqual(paths(*cs.get_nodes('bot/build/templates')), ['bot/build/templates/err.html', 'bot/build/templates/err2.html']) + self.assertEqual(paths(*cs.get_nodes('bot/templates/')), ['bot/templates/404.html', 'bot/templates/500.html']) + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_hg.py b/rhodecode/tests/vcs/test_hg.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_hg.py @@ -0,0 +1,557 @@ +from __future__ import with_statement + +import os +from rhodecode.lib.vcs.backends.hg import MercurialRepository, MercurialChangeset +from rhodecode.lib.vcs.exceptions import RepositoryError, VCSError, NodeDoesNotExistError +from rhodecode.lib.vcs.nodes import NodeKind, NodeState +from conf import PACKAGE_DIR, TEST_HG_REPO, TEST_HG_REPO_CLONE, \ + TEST_HG_REPO_PULL +from rhodecode.lib.vcs.utils.compat import unittest + + +# Use only clean mercurial's ui +import mercurial.scmutil +mercurial.scmutil.rcpath() +if mercurial.scmutil._rcpath: + mercurial.scmutil._rcpath = mercurial.scmutil._rcpath[:1] + + +class MercurialRepositoryTest(unittest.TestCase): + + def __check_for_existing_repo(self): + if os.path.exists(TEST_HG_REPO_CLONE): + self.fail('Cannot test mercurial clone repo as location %s already ' + 'exists. You should manually remove it first.' + % TEST_HG_REPO_CLONE) + + def setUp(self): + self.repo = MercurialRepository(TEST_HG_REPO) + + def test_wrong_repo_path(self): + wrong_repo_path = '/tmp/errorrepo' + self.assertRaises(RepositoryError, MercurialRepository, wrong_repo_path) + + def test_unicode_path_repo(self): + self.assertRaises(VCSError,lambda:MercurialRepository(u'iShouldFail')) + + def test_repo_clone(self): + self.__check_for_existing_repo() + repo = MercurialRepository(TEST_HG_REPO) + repo_clone = MercurialRepository(TEST_HG_REPO_CLONE, + src_url=TEST_HG_REPO, update_after_clone=True) + self.assertEqual(len(repo.revisions), len(repo_clone.revisions)) + # Checking hashes of changesets should be enough + for changeset in repo.get_changesets(): + raw_id = changeset.raw_id + self.assertEqual(raw_id, repo_clone.get_changeset(raw_id).raw_id) + + def test_repo_clone_with_update(self): + repo = MercurialRepository(TEST_HG_REPO) + repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_w_update', + src_url=TEST_HG_REPO, update_after_clone=True) + self.assertEqual(len(repo.revisions), len(repo_clone.revisions)) + + #check if current workdir was updated + self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \ + + '_w_update', + 'MANIFEST.in')), True,) + + def test_repo_clone_without_update(self): + repo = MercurialRepository(TEST_HG_REPO) + repo_clone = MercurialRepository(TEST_HG_REPO_CLONE + '_wo_update', + src_url=TEST_HG_REPO, update_after_clone=False) + self.assertEqual(len(repo.revisions), len(repo_clone.revisions)) + self.assertEqual(os.path.isfile(os.path.join(TEST_HG_REPO_CLONE \ + + '_wo_update', + 'MANIFEST.in')), False,) + + def test_pull(self): + if os.path.exists(TEST_HG_REPO_PULL): + self.fail('Cannot test mercurial pull command as location %s ' + 'already exists. You should manually remove it first' + % TEST_HG_REPO_PULL) + repo_new = MercurialRepository(TEST_HG_REPO_PULL, create=True) + self.assertTrue(len(self.repo.revisions) > len(repo_new.revisions)) + + repo_new.pull(self.repo.path) + repo_new = MercurialRepository(TEST_HG_REPO_PULL) + self.assertTrue(len(self.repo.revisions) == len(repo_new.revisions)) + + def test_revisions(self): + # there are 21 revisions at bitbucket now + # so we can assume they would be available from now on + subset = set(['b986218ba1c9b0d6a259fac9b050b1724ed8e545', + '3d8f361e72ab303da48d799ff1ac40d5ac37c67e', + '6cba7170863a2411822803fa77a0a264f1310b35', + '56349e29c2af3ac913b28bde9a2c6154436e615b', + '2dda4e345facb0ccff1a191052dd1606dba6781d', + '6fff84722075f1607a30f436523403845f84cd9e', + '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7', + '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb', + 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c', + 'be90031137367893f1c406e0a8683010fd115b79', + 'db8e58be770518cbb2b1cdfa69146e47cd481481', + '84478366594b424af694a6c784cb991a16b87c21', + '17f8e105dddb9f339600389c6dc7175d395a535c', + '20a662e756499bde3095ffc9bc0643d1def2d0eb', + '2e319b85e70a707bba0beff866d9f9de032aa4f9', + '786facd2c61deb9cf91e9534735124fb8fc11842', + '94593d2128d38210a2fcd1aabff6dda0d6d9edf8', + 'aa6a0de05b7612707db567078e130a6cd114a9a7', + 'eada5a770da98ab0dd7325e29d00e0714f228d09' + ]) + self.assertTrue(subset.issubset(set(self.repo.revisions))) + + + # check if we have the proper order of revisions + org = ['b986218ba1c9b0d6a259fac9b050b1724ed8e545', + '3d8f361e72ab303da48d799ff1ac40d5ac37c67e', + '6cba7170863a2411822803fa77a0a264f1310b35', + '56349e29c2af3ac913b28bde9a2c6154436e615b', + '2dda4e345facb0ccff1a191052dd1606dba6781d', + '6fff84722075f1607a30f436523403845f84cd9e', + '7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7', + '3803844fdbd3b711175fc3da9bdacfcd6d29a6fb', + 'dc5d2c0661b61928834a785d3e64a3f80d3aad9c', + 'be90031137367893f1c406e0a8683010fd115b79', + 'db8e58be770518cbb2b1cdfa69146e47cd481481', + '84478366594b424af694a6c784cb991a16b87c21', + '17f8e105dddb9f339600389c6dc7175d395a535c', + '20a662e756499bde3095ffc9bc0643d1def2d0eb', + '2e319b85e70a707bba0beff866d9f9de032aa4f9', + '786facd2c61deb9cf91e9534735124fb8fc11842', + '94593d2128d38210a2fcd1aabff6dda0d6d9edf8', + 'aa6a0de05b7612707db567078e130a6cd114a9a7', + 'eada5a770da98ab0dd7325e29d00e0714f228d09', + '2c1885c735575ca478bf9e17b0029dca68824458', + 'd9bcd465040bf869799b09ad732c04e0eea99fe9', + '469e9c847fe1f6f7a697b8b25b4bc5b48780c1a7', + '4fb8326d78e5120da2c7468dcf7098997be385da', + '62b4a097164940bd66030c4db51687f3ec035eed', + '536c1a19428381cfea92ac44985304f6a8049569', + '965e8ab3c44b070cdaa5bf727ddef0ada980ecc4', + '9bb326a04ae5d98d437dece54be04f830cf1edd9', + 'f8940bcb890a98c4702319fbe36db75ea309b475', + 'ff5ab059786ebc7411e559a2cc309dfae3625a3b', + '6b6ad5f82ad5bb6190037671bd254bd4e1f4bf08', + 'ee87846a61c12153b51543bf860e1026c6d3dcba', ] + self.assertEqual(org, self.repo.revisions[:31]) + + def test_iter_slice(self): + sliced = list(self.repo[:10]) + itered = list(self.repo)[:10] + self.assertEqual(sliced, itered) + + def test_slicing(self): + #4 1 5 10 95 + for sfrom, sto, size in [(0, 4, 4), (1, 2, 1), (10, 15, 5), + (10, 20, 10), (5, 100, 95)]: + revs = list(self.repo[sfrom:sto]) + self.assertEqual(len(revs), size) + self.assertEqual(revs[0], self.repo.get_changeset(sfrom)) + self.assertEqual(revs[-1], self.repo.get_changeset(sto - 1)) + + def test_branches(self): + # TODO: Need more tests here + + #active branches + self.assertTrue('default' in self.repo.branches) + self.assertTrue('git' in self.repo.branches) + + # closed + self.assertTrue('web' in self.repo._get_branches(closed=True)) + + for name, id in self.repo.branches.items(): + self.assertTrue(isinstance( + self.repo.get_changeset(id), MercurialChangeset)) + + def test_tip_in_tags(self): + # tip is always a tag + self.assertIn('tip', self.repo.tags) + + def test_tip_changeset_in_tags(self): + tip = self.repo.get_changeset() + self.assertEqual(self.repo.tags['tip'], tip.raw_id) + + def test_initial_changeset(self): + + init_chset = self.repo.get_changeset(0) + self.assertEqual(init_chset.message, 'initial import') + self.assertEqual(init_chset.author, + 'Marcin Kuzminski ') + self.assertEqual(sorted(init_chset._file_paths), + sorted([ + 'vcs/__init__.py', + 'vcs/backends/BaseRepository.py', + 'vcs/backends/__init__.py', + ]) + ) + self.assertEqual(sorted(init_chset._dir_paths), + sorted(['', 'vcs', 'vcs/backends'])) + + self.assertRaises(NodeDoesNotExistError, init_chset.get_node, path='foobar') + + node = init_chset.get_node('vcs/') + self.assertTrue(hasattr(node, 'kind')) + self.assertEqual(node.kind, NodeKind.DIR) + + node = init_chset.get_node('vcs') + self.assertTrue(hasattr(node, 'kind')) + self.assertEqual(node.kind, NodeKind.DIR) + + node = init_chset.get_node('vcs/__init__.py') + self.assertTrue(hasattr(node, 'kind')) + self.assertEqual(node.kind, NodeKind.FILE) + + def test_not_existing_changeset(self): + #rawid + self.assertRaises(RepositoryError, self.repo.get_changeset, + 'abcd' * 10) + #shortid + self.assertRaises(RepositoryError, self.repo.get_changeset, + 'erro' * 4) + #numeric + self.assertRaises(RepositoryError, self.repo.get_changeset, + self.repo.count() + 1) + + + # Small chance we ever get to this one + revision = pow(2, 30) + self.assertRaises(RepositoryError, self.repo.get_changeset, revision) + + def test_changeset10(self): + + chset10 = self.repo.get_changeset(10) + README = """=== +VCS +=== + +Various Version Control System management abstraction layer for Python. + +Introduction +------------ + +TODO: To be written... + +""" + node = chset10.get_node('README.rst') + self.assertEqual(node.kind, NodeKind.FILE) + self.assertEqual(node.content, README) + + +class MercurialChangesetTest(unittest.TestCase): + + def setUp(self): + self.repo = MercurialRepository(TEST_HG_REPO) + + def _test_equality(self, changeset): + revision = changeset.revision + self.assertEqual(changeset, self.repo.get_changeset(revision)) + + def test_equality(self): + self.setUp() + revs = [0, 10, 20] + changesets = [self.repo.get_changeset(rev) for rev in revs] + for changeset in changesets: + self._test_equality(changeset) + + def test_default_changeset(self): + tip = self.repo.get_changeset('tip') + self.assertEqual(tip, self.repo.get_changeset()) + self.assertEqual(tip, self.repo.get_changeset(revision=None)) + self.assertEqual(tip, list(self.repo[-1:])[0]) + + def test_root_node(self): + tip = self.repo.get_changeset('tip') + self.assertTrue(tip.root is tip.get_node('')) + + def test_lazy_fetch(self): + """ + Test if changeset's nodes expands and are cached as we walk through + the revision. This test is somewhat hard to write as order of tests + is a key here. Written by running command after command in a shell. + """ + self.setUp() + chset = self.repo.get_changeset(45) + self.assertTrue(len(chset.nodes) == 0) + root = chset.root + self.assertTrue(len(chset.nodes) == 1) + self.assertTrue(len(root.nodes) == 8) + # accessing root.nodes updates chset.nodes + self.assertTrue(len(chset.nodes) == 9) + + docs = root.get_node('docs') + # we haven't yet accessed anything new as docs dir was already cached + self.assertTrue(len(chset.nodes) == 9) + self.assertTrue(len(docs.nodes) == 8) + # accessing docs.nodes updates chset.nodes + self.assertTrue(len(chset.nodes) == 17) + + self.assertTrue(docs is chset.get_node('docs')) + self.assertTrue(docs is root.nodes[0]) + self.assertTrue(docs is root.dirs[0]) + self.assertTrue(docs is chset.get_node('docs')) + + def test_nodes_with_changeset(self): + self.setUp() + chset = self.repo.get_changeset(45) + root = chset.root + docs = root.get_node('docs') + self.assertTrue(docs is chset.get_node('docs')) + api = docs.get_node('api') + self.assertTrue(api is chset.get_node('docs/api')) + index = api.get_node('index.rst') + self.assertTrue(index is chset.get_node('docs/api/index.rst')) + self.assertTrue(index is chset.get_node('docs')\ + .get_node('api')\ + .get_node('index.rst')) + + def test_branch_and_tags(self): + chset0 = self.repo.get_changeset(0) + self.assertEqual(chset0.branch, 'default') + self.assertEqual(chset0.tags, []) + + chset10 = self.repo.get_changeset(10) + self.assertEqual(chset10.branch, 'default') + self.assertEqual(chset10.tags, []) + + chset44 = self.repo.get_changeset(44) + self.assertEqual(chset44.branch, 'web') + + tip = self.repo.get_changeset('tip') + self.assertTrue('tip' in tip.tags) + + def _test_file_size(self, revision, path, size): + node = self.repo.get_changeset(revision).get_node(path) + self.assertTrue(node.is_file()) + self.assertEqual(node.size, size) + + def test_file_size(self): + to_check = ( + (10, 'setup.py', 1068), + (20, 'setup.py', 1106), + (60, 'setup.py', 1074), + + (10, 'vcs/backends/base.py', 2921), + (20, 'vcs/backends/base.py', 3936), + (60, 'vcs/backends/base.py', 6189), + ) + for revision, path, size in to_check: + self._test_file_size(revision, path, size) + + def test_file_history(self): + # we can only check if those revisions are present in the history + # as we cannot update this test every time file is changed + files = { + 'setup.py': [7, 18, 45, 46, 47, 69, 77], + 'vcs/nodes.py': [7, 8, 24, 26, 30, 45, 47, 49, 56, 57, 58, 59, 60, + 61, 73, 76], + 'vcs/backends/hg.py': [4, 5, 6, 11, 12, 13, 14, 15, 16, 21, 22, 23, + 26, 27, 28, 30, 31, 33, 35, 36, 37, 38, 39, 40, 41, 44, 45, 47, + 48, 49, 53, 54, 55, 58, 60, 61, 67, 68, 69, 70, 73, 77, 78, 79, + 82], + } + for path, revs in files.items(): + tip = self.repo.get_changeset(revs[-1]) + node = tip.get_node(path) + node_revs = [chset.revision for chset in node.history] + self.assertTrue(set(revs).issubset(set(node_revs)), + "We assumed that %s is subset of revisions for which file %s " + "has been changed, and history of that node returned: %s" + % (revs, path, node_revs)) + + def test_file_annotate(self): + files = { + 'vcs/backends/__init__.py': + {89: {'lines_no': 31, + 'changesets': [32, 32, 61, 32, 32, 37, 32, 32, 32, 44, + 37, 37, 37, 37, 45, 37, 44, 37, 37, 37, + 32, 32, 32, 32, 37, 32, 37, 37, 32, + 32, 32]}, + 20: {'lines_no': 1, + 'changesets': [4]}, + 55: {'lines_no': 31, + 'changesets': [32, 32, 45, 32, 32, 37, 32, 32, 32, 44, + 37, 37, 37, 37, 45, 37, 44, 37, 37, 37, + 32, 32, 32, 32, 37, 32, 37, 37, 32, + 32, 32]}}, + 'vcs/exceptions.py': + {89: {'lines_no': 18, + 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 17, 16, 16, 18, 18, 18]}, + 20: {'lines_no': 18, + 'changesets': [16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 17, 16, 16, 18, 18, 18]}, + 55: {'lines_no': 18, 'changesets': [16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, + 17, 16, 16, 18, 18, 18]}}, + 'MANIFEST.in': {89: {'lines_no': 5, + 'changesets': [7, 7, 7, 71, 71]}, + 20: {'lines_no': 3, + 'changesets': [7, 7, 7]}, + 55: {'lines_no': 3, + 'changesets': [7, 7, 7]}}} + + + for fname, revision_dict in files.items(): + for rev, data in revision_dict.items(): + cs = self.repo.get_changeset(rev) + ann = cs.get_file_annotate(fname) + + l1 = [x[1].revision for x in ann] + l2 = files[fname][rev]['changesets'] + self.assertTrue(l1 == l2 , "The lists of revision for %s@rev%s" + "from annotation list should match each other," + "got \n%s \nvs \n%s " % (fname, rev, l1, l2)) + + def test_changeset_state(self): + """ + Tests which files have been added/changed/removed at particular revision + """ + + # rev 46ad32a4f974: + # hg st --rev 46ad32a4f974 + # changed: 13 + # added: 20 + # removed: 1 + changed = set(['.hgignore' + , 'README.rst' , 'docs/conf.py' , 'docs/index.rst' , 'setup.py' + , 'tests/test_hg.py' , 'tests/test_nodes.py' , 'vcs/__init__.py' + , 'vcs/backends/__init__.py' , 'vcs/backends/base.py' + , 'vcs/backends/hg.py' , 'vcs/nodes.py' , 'vcs/utils/__init__.py']) + + added = set(['docs/api/backends/hg.rst' + , 'docs/api/backends/index.rst' , 'docs/api/index.rst' + , 'docs/api/nodes.rst' , 'docs/api/web/index.rst' + , 'docs/api/web/simplevcs.rst' , 'docs/installation.rst' + , 'docs/quickstart.rst' , 'setup.cfg' , 'vcs/utils/baseui_config.py' + , 'vcs/utils/web.py' , 'vcs/web/__init__.py' , 'vcs/web/exceptions.py' + , 'vcs/web/simplevcs/__init__.py' , 'vcs/web/simplevcs/exceptions.py' + , 'vcs/web/simplevcs/middleware.py' , 'vcs/web/simplevcs/models.py' + , 'vcs/web/simplevcs/settings.py' , 'vcs/web/simplevcs/utils.py' + , 'vcs/web/simplevcs/views.py']) + + removed = set(['docs/api.rst']) + + chset64 = self.repo.get_changeset('46ad32a4f974') + self.assertEqual(set((node.path for node in chset64.added)), added) + self.assertEqual(set((node.path for node in chset64.changed)), changed) + self.assertEqual(set((node.path for node in chset64.removed)), removed) + + # rev b090f22d27d6: + # hg st --rev b090f22d27d6 + # changed: 13 + # added: 20 + # removed: 1 + chset88 = self.repo.get_changeset('b090f22d27d6') + self.assertEqual(set((node.path for node in chset88.added)), set()) + self.assertEqual(set((node.path for node in chset88.changed)), + set(['.hgignore'])) + self.assertEqual(set((node.path for node in chset88.removed)), set()) +# + # 85: + # added: 2 ['vcs/utils/diffs.py', 'vcs/web/simplevcs/views/diffs.py'] + # changed: 4 ['vcs/web/simplevcs/models.py', ...] + # removed: 1 ['vcs/utils/web.py'] + chset85 = self.repo.get_changeset(85) + self.assertEqual(set((node.path for node in chset85.added)), set([ + 'vcs/utils/diffs.py', + 'vcs/web/simplevcs/views/diffs.py'])) + self.assertEqual(set((node.path for node in chset85.changed)), set([ + 'vcs/web/simplevcs/models.py', + 'vcs/web/simplevcs/utils.py', + 'vcs/web/simplevcs/views/__init__.py', + 'vcs/web/simplevcs/views/repository.py', + ])) + self.assertEqual(set((node.path for node in chset85.removed)), + set(['vcs/utils/web.py'])) + + + def test_files_state(self): + """ + Tests state of FileNodes. + """ + chset = self.repo.get_changeset(85) + node = chset.get_node('vcs/utils/diffs.py') + self.assertTrue(node.state, NodeState.ADDED) + self.assertTrue(node.added) + self.assertFalse(node.changed) + self.assertFalse(node.not_changed) + self.assertFalse(node.removed) + + chset = self.repo.get_changeset(88) + node = chset.get_node('.hgignore') + self.assertTrue(node.state, NodeState.CHANGED) + self.assertFalse(node.added) + self.assertTrue(node.changed) + self.assertFalse(node.not_changed) + self.assertFalse(node.removed) + + chset = self.repo.get_changeset(85) + node = chset.get_node('setup.py') + self.assertTrue(node.state, NodeState.NOT_CHANGED) + self.assertFalse(node.added) + self.assertFalse(node.changed) + self.assertTrue(node.not_changed) + self.assertFalse(node.removed) + + # If node has REMOVED state then trying to fetch it would raise + # ChangesetError exception + chset = self.repo.get_changeset(2) + path = 'vcs/backends/BaseRepository.py' + self.assertRaises(NodeDoesNotExistError, chset.get_node, path) + # but it would be one of ``removed`` (changeset's attribute) + self.assertTrue(path in [rf.path for rf in chset.removed]) + + def test_commit_message_is_unicode(self): + for cm in self.repo: + self.assertEqual(type(cm.message), unicode) + + def test_changeset_author_is_unicode(self): + for cm in self.repo: + self.assertEqual(type(cm.author), unicode) + + def test_repo_files_content_is_unicode(self): + test_changeset = self.repo.get_changeset(100) + for node in test_changeset.get_node('/'): + if node.is_file(): + self.assertEqual(type(node.content), unicode) + + def test_wrong_path(self): + # There is 'setup.py' in the root dir but not there: + path = 'foo/bar/setup.py' + self.assertRaises(VCSError, self.repo.get_changeset().get_node, path) + + + def test_archival_file(self): + #TODO: + pass + + def test_archival_as_generator(self): + #TODO: + pass + + def test_archival_wrong_kind(self): + tip = self.repo.get_changeset() + self.assertRaises(VCSError, tip.fill_archive, kind='error') + + def test_archival_empty_prefix(self): + #TODO: + pass + + + def test_author_email(self): + self.assertEqual('marcin@python-blog.com', + self.repo.get_changeset('b986218ba1c9').author_email) + self.assertEqual('lukasz.balcerzak@python-center.pl', + self.repo.get_changeset('3803844fdbd3').author_email) + self.assertEqual('', + self.repo.get_changeset('84478366594b').author_email) + + def test_author_username(self): + self.assertEqual('Marcin Kuzminski', + self.repo.get_changeset('b986218ba1c9').author_name) + self.assertEqual('Lukasz Balcerzak', + self.repo.get_changeset('3803844fdbd3').author_name) + self.assertEqual('marcink', + self.repo.get_changeset('84478366594b').author_name) diff --git a/rhodecode/tests/vcs/test_inmemchangesets.py b/rhodecode/tests/vcs/test_inmemchangesets.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_inmemchangesets.py @@ -0,0 +1,340 @@ +""" +Tests so called "in memory changesets" commit API of vcs. +""" +from __future__ import with_statement + +from rhodecode.lib import vcs +import time +import datetime +from conf import SCM_TESTS, get_new_dir +from rhodecode.lib.vcs.exceptions import EmptyRepositoryError +from rhodecode.lib.vcs.exceptions import NodeAlreadyAddedError +from rhodecode.lib.vcs.exceptions import NodeAlreadyExistsError +from rhodecode.lib.vcs.exceptions import NodeAlreadyRemovedError +from rhodecode.lib.vcs.exceptions import NodeAlreadyChangedError +from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError +from rhodecode.lib.vcs.exceptions import NodeNotChangedError +from rhodecode.lib.vcs.nodes import DirNode +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.utils.compat import unittest + + +class InMemoryChangesetTestMixin(object): + """ + This is a backend independent test case class which should be created + with ``type`` method. + + It is required to set following attributes at subclass: + + - ``backend_alias``: alias of used backend (see ``vcs.BACKENDS``) + - ``repo_path``: path to the repository which would be created for set of + tests + """ + + def get_backend(self): + return vcs.get_backend(self.backend_alias) + + def setUp(self): + Backend = self.get_backend() + self.repo_path = get_new_dir(str(time.time())) + self.repo = Backend(self.repo_path, create=True) + self.imc = self.repo.in_memory_changeset + self.nodes = [ + FileNode('foobar', content='Foo & bar'), + FileNode('foobar2', content='Foo & bar, doubled!'), + FileNode('foo bar with spaces', content=''), + FileNode('foo/bar/baz', content='Inside'), + ] + + def test_add(self): + rev_count = len(self.repo.revisions) + to_add = [FileNode(node.path, content=node.content) + for node in self.nodes] + for node in to_add: + self.imc.add(node) + message = u'Added: %s' % ', '.join((node.path for node in self.nodes)) + author = unicode(self.__class__) + changeset = self.imc.commit(message=message, author=author) + + newtip = self.repo.get_changeset() + self.assertEqual(changeset, newtip) + self.assertEqual(rev_count + 1, len(self.repo.revisions)) + self.assertEqual(newtip.message, message) + self.assertEqual(newtip.author, author) + self.assertTrue(not any((self.imc.added, self.imc.changed, + self.imc.removed))) + for node in to_add: + self.assertEqual(newtip.get_node(node.path).content, node.content) + + def test_add_in_bulk(self): + rev_count = len(self.repo.revisions) + to_add = [FileNode(node.path, content=node.content) + for node in self.nodes] + self.imc.add(*to_add) + message = u'Added: %s' % ', '.join((node.path for node in self.nodes)) + author = unicode(self.__class__) + changeset = self.imc.commit(message=message, author=author) + + newtip = self.repo.get_changeset() + self.assertEqual(changeset, newtip) + self.assertEqual(rev_count + 1, len(self.repo.revisions)) + self.assertEqual(newtip.message, message) + self.assertEqual(newtip.author, author) + self.assertTrue(not any((self.imc.added, self.imc.changed, + self.imc.removed))) + for node in to_add: + self.assertEqual(newtip.get_node(node.path).content, node.content) + + def test_add_actually_adds_all_nodes_at_second_commit_too(self): + self.imc.add(FileNode('foo/bar/image.png', content='\0')) + self.imc.add(FileNode('foo/README.txt', content='readme!')) + changeset = self.imc.commit(u'Initial', u'joe.doe@example.com') + self.assertTrue(isinstance(changeset.get_node('foo'), DirNode)) + self.assertTrue(isinstance(changeset.get_node('foo/bar'), DirNode)) + self.assertEqual(changeset.get_node('foo/bar/image.png').content, '\0') + self.assertEqual(changeset.get_node('foo/README.txt').content, 'readme!') + + # commit some more files again + to_add = [ + FileNode('foo/bar/foobaz/bar', content='foo'), + FileNode('foo/bar/another/bar', content='foo'), + FileNode('foo/baz.txt', content='foo'), + FileNode('foobar/foobaz/file', content='foo'), + FileNode('foobar/barbaz', content='foo'), + ] + self.imc.add(*to_add) + changeset = self.imc.commit(u'Another', u'joe.doe@example.com') + self.assertEqual(changeset.get_node('foo/bar/foobaz/bar').content, 'foo') + self.assertEqual(changeset.get_node('foo/bar/another/bar').content, 'foo') + self.assertEqual(changeset.get_node('foo/baz.txt').content, 'foo') + self.assertEqual(changeset.get_node('foobar/foobaz/file').content, 'foo') + self.assertEqual(changeset.get_node('foobar/barbaz').content, 'foo') + + def test_add_raise_already_added(self): + node = FileNode('foobar', content='baz') + self.imc.add(node) + self.assertRaises(NodeAlreadyAddedError, self.imc.add, node) + + def test_check_integrity_raise_already_exist(self): + node = FileNode('foobar', content='baz') + self.imc.add(node) + self.imc.commit(message=u'Added foobar', author=unicode(self)) + self.imc.add(node) + self.assertRaises(NodeAlreadyExistsError, self.imc.commit, + message='new message', + author=str(self)) + + def test_change(self): + self.imc.add(FileNode('foo/bar/baz', content='foo')) + self.imc.add(FileNode('foo/fbar', content='foobar')) + tip = self.imc.commit(u'Initial', u'joe.doe@example.com') + + # Change node's content + node = FileNode('foo/bar/baz', content='My **changed** content') + self.imc.change(node) + self.imc.commit(u'Changed %s' % node.path, u'joe.doe@example.com') + + newtip = self.repo.get_changeset() + self.assertNotEqual(tip, newtip) + self.assertNotEqual(tip.id, newtip.id) + self.assertEqual(newtip.get_node('foo/bar/baz').content, + 'My **changed** content') + + def test_change_raise_empty_repository(self): + node = FileNode('foobar') + self.assertRaises(EmptyRepositoryError, self.imc.change, node) + + def test_check_integrity_change_raise_node_does_not_exist(self): + node = FileNode('foobar', content='baz') + self.imc.add(node) + self.imc.commit(message=u'Added foobar', author=unicode(self)) + node = FileNode('not-foobar', content='') + self.imc.change(node) + self.assertRaises(NodeDoesNotExistError, self.imc.commit, + message='Changed not existing node', + author=str(self)) + + def test_change_raise_node_already_changed(self): + node = FileNode('foobar', content='baz') + self.imc.add(node) + self.imc.commit(message=u'Added foobar', author=unicode(self)) + node = FileNode('foobar', content='more baz') + self.imc.change(node) + self.assertRaises(NodeAlreadyChangedError, self.imc.change, node) + + def test_check_integrity_change_raise_node_not_changed(self): + self.test_add() # Performs first commit + + node = FileNode(self.nodes[0].path, content=self.nodes[0].content) + self.imc.change(node) + self.assertRaises(NodeNotChangedError, self.imc.commit, + message=u'Trying to mark node as changed without touching it', + author=unicode(self)) + + def test_change_raise_node_already_removed(self): + node = FileNode('foobar', content='baz') + self.imc.add(node) + self.imc.commit(message=u'Added foobar', author=unicode(self)) + self.imc.remove(FileNode('foobar')) + self.assertRaises(NodeAlreadyRemovedError, self.imc.change, node) + + def test_remove(self): + self.test_add() # Performs first commit + + tip = self.repo.get_changeset() + node = self.nodes[0] + self.assertEqual(node.content, tip.get_node(node.path).content) + self.imc.remove(node) + self.imc.commit(message=u'Removed %s' % node.path, author=unicode(self)) + + newtip = self.repo.get_changeset() + self.assertNotEqual(tip, newtip) + self.assertNotEqual(tip.id, newtip.id) + self.assertRaises(NodeDoesNotExistError, newtip.get_node, node.path) + + def test_remove_last_file_from_directory(self): + node = FileNode('omg/qwe/foo/bar', content='foobar') + self.imc.add(node) + self.imc.commit(u'added', u'joe doe') + + self.imc.remove(node) + tip = self.imc.commit(u'removed', u'joe doe') + self.assertRaises(NodeDoesNotExistError, tip.get_node, 'omg/qwe/foo/bar') + + def test_remove_raise_node_does_not_exist(self): + self.imc.remove(self.nodes[0]) + self.assertRaises(NodeDoesNotExistError, self.imc.commit, + message='Trying to remove node at empty repository', + author=str(self)) + + def test_check_integrity_remove_raise_node_does_not_exist(self): + self.test_add() # Performs first commit + + node = FileNode('no-such-file') + self.imc.remove(node) + self.assertRaises(NodeDoesNotExistError, self.imc.commit, + message=u'Trying to remove not existing node', + author=unicode(self)) + + def test_remove_raise_node_already_removed(self): + self.test_add() # Performs first commit + + node = FileNode(self.nodes[0].path) + self.imc.remove(node) + self.assertRaises(NodeAlreadyRemovedError, self.imc.remove, node) + + def test_remove_raise_node_already_changed(self): + self.test_add() # Performs first commit + + node = FileNode(self.nodes[0].path, content='Bending time') + self.imc.change(node) + self.assertRaises(NodeAlreadyChangedError, self.imc.remove, node) + + def test_reset(self): + self.imc.add(FileNode('foo', content='bar')) + #self.imc.change(FileNode('baz', content='new')) + #self.imc.remove(FileNode('qwe')) + self.imc.reset() + self.assertTrue(not any((self.imc.added, self.imc.changed, + self.imc.removed))) + + def test_multiple_commits(self): + N = 3 # number of commits to perform + last = None + for x in xrange(N): + fname = 'file%s' % str(x).rjust(5, '0') + content = 'foobar\n' * x + node = FileNode(fname, content=content) + self.imc.add(node) + commit = self.imc.commit(u"Commit no. %s" % (x + 1), author=u'vcs') + self.assertTrue(last != commit) + last = commit + + # Check commit number for same repo + self.assertEqual(len(self.repo.revisions), N) + + # Check commit number for recreated repo + backend = self.get_backend() + repo = backend(self.repo_path) + self.assertEqual(len(repo.revisions), N) + + def test_date_attr(self): + node = FileNode('foobar.txt', content='Foobared!') + self.imc.add(node) + date = datetime.datetime(1985, 1, 30, 1, 45) + commit = self.imc.commit(u"Committed at time when I was born ;-)", + author=u'lb', date=date) + + self.assertEqual(commit.date, date) + + +class BackendBaseTestCase(unittest.TestCase): + """ + Base test class for tests which requires repository. + """ + backend_alias = 'hg' + commits = [ + { + 'message': 'Initial commit', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('foobar', content='Foobar'), + FileNode('foobar2', content='Foobar II'), + FileNode('foo/bar/baz', content='baz here!'), + ], + }, + ] + + def get_backend(self): + return vcs.get_backend(self.backend_alias) + + def get_commits(self): + """ + Returns list of commits which builds repository for each tests. + """ + if hasattr(self, 'commits'): + return self.commits + + def get_new_repo_path(self): + """ + Returns newly created repository's directory. + """ + backend = self.get_backend() + key = '%s-%s' % (backend.alias, str(time.time())) + repo_path = get_new_dir(key) + return repo_path + + def setUp(self): + Backend = self.get_backend() + self.backend_class = Backend + self.repo_path = self.get_new_repo_path() + self.repo = Backend(self.repo_path, create=True) + self.imc = self.repo.in_memory_changeset + + for commit in self.get_commits(): + for node in commit.get('added', []): + self.imc.add(FileNode(node.path, content=node.content)) + for node in commit.get('changed', []): + self.imc.change(FileNode(node.path, content=node.content)) + for node in commit.get('removed', []): + self.imc.remove(FileNode(node.path)) + self.imc.commit(message=unicode(commit['message']), + author=unicode(commit['author']), + date=commit['date']) + + self.tip = self.repo.get_changeset() + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s in memory changeset test' % alias).title().split()) + bases = (InMemoryChangesetTestMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_nodes.py b/rhodecode/tests/vcs/test_nodes.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_nodes.py @@ -0,0 +1,183 @@ +from __future__ import with_statement + +import stat +from rhodecode.lib.vcs.nodes import DirNode +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.nodes import Node +from rhodecode.lib.vcs.nodes import NodeError +from rhodecode.lib.vcs.nodes import NodeKind +from rhodecode.lib.vcs.utils.compat import unittest + + +class NodeBasicTest(unittest.TestCase): + + def test_init(self): + """ + Cannot innitialize Node objects with path with slash at the beginning. + """ + wrong_paths = ( + '/foo', + '/foo/bar' + ) + for path in wrong_paths: + self.assertRaises(NodeError, Node, path, NodeKind.FILE) + + wrong_paths = ( + '/foo/', + '/foo/bar/' + ) + for path in wrong_paths: + self.assertRaises(NodeError, Node, path, NodeKind.DIR) + + def test_name(self): + node = Node('', NodeKind.DIR) + self.assertEqual(node.name, '') + + node = Node('path', NodeKind.FILE) + self.assertEqual(node.name, 'path') + + node = Node('path/', NodeKind.DIR) + self.assertEqual(node.name, 'path') + + node = Node('some/path', NodeKind.FILE) + self.assertEqual(node.name, 'path') + + node = Node('some/path/', NodeKind.DIR) + self.assertEqual(node.name, 'path') + + def test_root_node(self): + self.assertRaises(NodeError, Node, '', NodeKind.FILE) + + def test_kind_setter(self): + node = Node('', NodeKind.DIR) + self.assertRaises(NodeError, setattr, node, 'kind', NodeKind.FILE) + + def _test_parent_path(self, node_path, expected_parent_path): + """ + Tests if node's parent path are properly computed. + """ + node = Node(node_path, NodeKind.DIR) + parent_path = node.get_parent_path() + self.assertTrue(parent_path.endswith('/') or \ + node.is_root() and parent_path == '') + self.assertEqual(parent_path, expected_parent_path, + "Node's path is %r and parent path is %r but should be %r" + % (node.path, parent_path, expected_parent_path)) + + def test_parent_path(self): + test_paths = ( + # (node_path, expected_parent_path) + ('', ''), + ('some/path/', 'some/'), + ('some/longer/path/', 'some/longer/'), + ) + for node_path, expected_parent_path in test_paths: + self._test_parent_path(node_path, expected_parent_path) + + ''' + def _test_trailing_slash(self, path): + if not path.endswith('/'): + self.fail("Trailing slash tests needs paths to end with slash") + for kind in NodeKind.FILE, NodeKind.DIR: + self.assertRaises(NodeError, Node, path=path, kind=kind) + + def test_trailing_slash(self): + for path in ('/', 'foo/', 'foo/bar/', 'foo/bar/biz/'): + self._test_trailing_slash(path) + ''' + + def test_is_file(self): + node = Node('any', NodeKind.FILE) + self.assertTrue(node.is_file()) + + node = FileNode('any') + self.assertTrue(node.is_file()) + self.assertRaises(AttributeError, getattr, node, 'nodes') + + def test_is_dir(self): + node = Node('any_dir', NodeKind.DIR) + self.assertTrue(node.is_dir()) + + node = DirNode('any_dir') + + self.assertTrue(node.is_dir()) + self.assertRaises(NodeError, getattr, node, 'content') + + def test_dir_node_iter(self): + nodes = [ + DirNode('docs'), + DirNode('tests'), + FileNode('bar'), + FileNode('foo'), + FileNode('readme.txt'), + FileNode('setup.py'), + ] + dirnode = DirNode('', nodes=nodes) + for node in dirnode: + node == dirnode.get_node(node.path) + + def test_node_state(self): + """ + Without link to changeset nodes should raise NodeError. + """ + node = FileNode('anything') + self.assertRaises(NodeError, getattr, node, 'state') + node = DirNode('anything') + self.assertRaises(NodeError, getattr, node, 'state') + + def test_file_node_stat(self): + node = FileNode('foobar', 'empty... almost') + mode = node.mode # default should be 0100644 + self.assertTrue(mode & stat.S_IRUSR) + self.assertTrue(mode & stat.S_IWUSR) + self.assertTrue(mode & stat.S_IRGRP) + self.assertTrue(mode & stat.S_IROTH) + self.assertFalse(mode & stat.S_IWGRP) + self.assertFalse(mode & stat.S_IWOTH) + self.assertFalse(mode & stat.S_IXUSR) + self.assertFalse(mode & stat.S_IXGRP) + self.assertFalse(mode & stat.S_IXOTH) + + def test_file_node_is_executable(self): + node = FileNode('foobar', 'empty... almost', mode=0100755) + self.assertTrue(node.is_executable()) + + node = FileNode('foobar', 'empty... almost', mode=0100500) + self.assertTrue(node.is_executable()) + + node = FileNode('foobar', 'empty... almost', mode=0100644) + self.assertFalse(node.is_executable()) + + def test_mimetype(self): + py_node = FileNode('test.py') + tar_node = FileNode('test.tar.gz') + + ext = 'CustomExtension' + + my_node2 = FileNode('myfile2') + my_node2._mimetype = [ext] + + my_node3 = FileNode('myfile3') + my_node3._mimetype = [ext,ext] + + self.assertEqual(py_node.mimetype,'text/x-python') + self.assertEqual(py_node.get_mimetype(),('text/x-python',None)) + + self.assertEqual(tar_node.mimetype,'application/x-tar') + self.assertEqual(tar_node.get_mimetype(),('application/x-tar','gzip')) + + self.assertRaises(NodeError,my_node2.get_mimetype) + + self.assertEqual(my_node3.mimetype,ext) + self.assertEqual(my_node3.get_mimetype(),[ext,ext]) + +class NodeContentTest(unittest.TestCase): + + def test_if_binary(self): + data = """\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f??a\x00\x00\x00\x04gAMA\x00\x00\xaf?7\x05\x8a?\x00\x00\x00\x19tEXtSoftware\x00Adobe ImageReadyq?e<\x00\x00\x025IDAT8?\xa5\x93?K\x94Q\x14\x87\x9f\xf7?Q\x1bs4?\x03\x9a\xa8?B\x02\x8b$\x10[U;i\x13?6h?&h[?"\x14j?\xa2M\x7fB\x14F\x9aQ?&\x842?\x0b\x89"\x82??!?\x9c!\x9c2l??{N\x8bW\x9dY\xb4\t/\x1c?=\x9b?}????\xa9*;9!?\x83\x91?[?\\v*?D\x04\'`EpNp\xa2X\'U?pVq"Sw.\x1e?\x08\x01D?jw????\xbc??7{|\x9b?\x89$\x01??W@\x15\x9c\x05q`Lt/\x97?\x94\xa1d?\x18~?\x18?\x18W[%\xb0?\x83??\x14\x88\x8dB?\xa6H\tL\tl\x19>/\x01`\xac\xabx?\x9cl\nx\xb0\x98\x07\x95\x88D$"q[\x19?d\x00(o\n\xa0??\x7f\xb9\xa4?\x1bF\x1f\x8e\xac\xa8?j??eUU}?.?\x9f\x8cE??x\x94??\r\xbdtoJU5"0N\x10U?\x00??V\t\x02\x9f\x81?U?\x00\x9eM\xae2?r\x9b7\x83\x82\x8aP3????.?&"?\xb7ZP \x0cJ?\x80\x15T\x95\x9a\x00??S\x8c\r?\xa1\x03\x07?\x96\x9b\xa7\xab=E??\xa4\xb3?\x19q??B\x91=\x8d??k?J\x0bV"??\xf7x?\xa1\x00?\\.\x87\x87???\x02F@D\x99],??\x10#?X\xb7=\xb9\x10?Z\x1by???cI??\x1ag?\x92\xbc?T?t[\x92\x81?<_\x17~\x92\x88?H%?\x10Q\x02\x9f\n\x81qQ\x0bm?\x1bX?\xb1AK\xa6\x9e\xb9?u\xb2?1\xbe|/\x92M@\xa2!F?\xa9>"\r\x92\x8e?>\x9a9Qv\x127?a\xac?Y?8?:??]X???9\x80\xb7?u?\x0b#BZ\x8d=\x1d?p\x00\x00\x00\x00IEND\xaeB`\x82""" + filenode = FileNode('calendar.png', content=data) + self.assertTrue(filenode.is_binary) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_repository.py b/rhodecode/tests/vcs/test_repository.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_repository.py @@ -0,0 +1,215 @@ +from __future__ import with_statement +import datetime +from base import BackendTestMixin +from conf import SCM_TESTS +from conf import TEST_USER_CONFIG_FILE +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.utils.compat import unittest +from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError + + +class RepositoryBaseTest(BackendTestMixin): + recreate_repo_per_test = False + + @classmethod + def _get_commits(cls): + return super(RepositoryBaseTest, cls)._get_commits()[:1] + + def test_get_config_value(self): + self.assertEqual(self.repo.get_config_value('universal', 'foo', + TEST_USER_CONFIG_FILE), 'bar') + + def test_get_config_value_defaults_to_None(self): + self.assertEqual(self.repo.get_config_value('universal', 'nonexist', + TEST_USER_CONFIG_FILE), None) + + def test_get_user_name(self): + self.assertEqual(self.repo.get_user_name(TEST_USER_CONFIG_FILE), + 'Foo Bar') + + def test_get_user_email(self): + self.assertEqual(self.repo.get_user_email(TEST_USER_CONFIG_FILE), + 'foo.bar@example.com') + + + +class RepositoryGetDiffTest(BackendTestMixin): + + @classmethod + def _get_commits(cls): + commits = [ + { + 'message': 'Initial commit', + 'author': 'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('foobar', content='foobar'), + FileNode('foobar2', content='foobar2'), + ], + }, + { + 'message': 'Changed foobar, added foobar3', + 'author': 'Jane Doe ', + 'date': datetime.datetime(2010, 1, 1, 21), + 'added': [ + FileNode('foobar3', content='foobar3'), + ], + 'changed': [ + FileNode('foobar', 'FOOBAR'), + ], + }, + { + 'message': 'Removed foobar, changed foobar3', + 'author': 'Jane Doe ', + 'date': datetime.datetime(2010, 1, 1, 22), + 'changed': [ + FileNode('foobar3', content='FOOBAR\nFOOBAR\nFOOBAR\n'), + ], + 'removed': [FileNode('foobar')], + }, + ] + return commits + + def test_raise_for_wrong(self): + with self.assertRaises(ChangesetDoesNotExistError): + self.repo.get_diff('a' * 40, 'b' * 40) + +class GitRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase): + backend_alias = 'git' + + def test_initial_commit_diff(self): + initial_rev = self.repo.revisions[0] + self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar +new file mode 100644 +index 0000000..f6ea049 +--- /dev/null ++++ b/foobar +@@ -0,0 +1 @@ ++foobar +\ No newline at end of file +diff --git a/foobar2 b/foobar2 +new file mode 100644 +index 0000000..e8c9d6b +--- /dev/null ++++ b/foobar2 +@@ -0,0 +1 @@ ++foobar2 +\ No newline at end of file +''') + + def test_second_changeset_diff(self): + revs = self.repo.revisions + self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar +index f6ea049..389865b 100644 +--- a/foobar ++++ b/foobar +@@ -1 +1 @@ +-foobar +\ No newline at end of file ++FOOBAR +\ No newline at end of file +diff --git a/foobar3 b/foobar3 +new file mode 100644 +index 0000000..c11c37d +--- /dev/null ++++ b/foobar3 +@@ -0,0 +1 @@ ++foobar3 +\ No newline at end of file +''') + + def test_third_changeset_diff(self): + revs = self.repo.revisions + self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar +deleted file mode 100644 +index 389865b..0000000 +--- a/foobar ++++ /dev/null +@@ -1 +0,0 @@ +-FOOBAR +\ No newline at end of file +diff --git a/foobar3 b/foobar3 +index c11c37d..f932447 100644 +--- a/foobar3 ++++ b/foobar3 +@@ -1 +1,3 @@ +-foobar3 +\ No newline at end of file ++FOOBAR ++FOOBAR ++FOOBAR +''') + + +class HgRepositoryGetDiffTest(RepositoryGetDiffTest, unittest.TestCase): + backend_alias = 'hg' + + def test_initial_commit_diff(self): + initial_rev = self.repo.revisions[0] + self.assertEqual(self.repo.get_diff(self.repo.EMPTY_CHANGESET, initial_rev), '''diff --git a/foobar b/foobar +new file mode 100755 +--- /dev/null ++++ b/foobar +@@ -0,0 +1,1 @@ ++foobar +\ No newline at end of file +diff --git a/foobar2 b/foobar2 +new file mode 100755 +--- /dev/null ++++ b/foobar2 +@@ -0,0 +1,1 @@ ++foobar2 +\ No newline at end of file +''') + + def test_second_changeset_diff(self): + revs = self.repo.revisions + self.assertEqual(self.repo.get_diff(revs[0], revs[1]), '''diff --git a/foobar b/foobar +--- a/foobar ++++ b/foobar +@@ -1,1 +1,1 @@ +-foobar +\ No newline at end of file ++FOOBAR +\ No newline at end of file +diff --git a/foobar3 b/foobar3 +new file mode 100755 +--- /dev/null ++++ b/foobar3 +@@ -0,0 +1,1 @@ ++foobar3 +\ No newline at end of file +''') + + def test_third_changeset_diff(self): + revs = self.repo.revisions + self.assertEqual(self.repo.get_diff(revs[1], revs[2]), '''diff --git a/foobar b/foobar +deleted file mode 100755 +--- a/foobar ++++ /dev/null +@@ -1,1 +0,0 @@ +-FOOBAR +\ No newline at end of file +diff --git a/foobar3 b/foobar3 +--- a/foobar3 ++++ b/foobar3 +@@ -1,1 +1,3 @@ +-foobar3 +\ No newline at end of file ++FOOBAR ++FOOBAR ++FOOBAR +''') + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = alias.capitalize() + RepositoryBaseTest.__name__ + bases = (RepositoryBaseTest, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_tags.py b/rhodecode/tests/vcs/test_tags.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_tags.py @@ -0,0 +1,61 @@ +from __future__ import with_statement + +from base import BackendTestMixin +from conf import SCM_TESTS +from rhodecode.lib.vcs.exceptions import TagAlreadyExistError +from rhodecode.lib.vcs.exceptions import TagDoesNotExistError +from rhodecode.lib.vcs.utils.compat import unittest + + +class TagsTestCaseMixin(BackendTestMixin): + + def test_new_tag(self): + tip = self.repo.get_changeset() + tagsize = len(self.repo.tags) + tag = self.repo.tag('last-commit', 'joe', tip.raw_id) + + self.assertEqual(len(self.repo.tags), tagsize + 1) + for top, dirs, files in tip.walk(): + self.assertEqual(top, tag.get_node(top.path)) + + def test_tag_already_exist(self): + tip = self.repo.get_changeset() + self.repo.tag('last-commit', 'joe', tip.raw_id) + + self.assertRaises(TagAlreadyExistError, + self.repo.tag, 'last-commit', 'joe', tip.raw_id) + + chset = self.repo.get_changeset(0) + self.assertRaises(TagAlreadyExistError, + self.repo.tag, 'last-commit', 'jane', chset.raw_id) + + def test_remove_tag(self): + tip = self.repo.get_changeset() + self.repo.tag('last-commit', 'joe', tip.raw_id) + tagsize = len(self.repo.tags) + + self.repo.remove_tag('last-commit', user='evil joe') + self.assertEqual(len(self.repo.tags), tagsize - 1) + + def test_remove_tag_which_does_not_exist(self): + self.assertRaises(TagDoesNotExistError, + self.repo.remove_tag, 'last-commit', user='evil joe') + + def test_name_with_slash(self): + self.repo.tag('19/10/11', 'joe') + self.assertTrue('19/10/11' in self.repo.tags) + self.repo.tag('11', 'joe') + self.assertTrue('11' in self.repo.tags) + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s tags test' % alias).title().split()) + bases = (TagsTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_utils.py b/rhodecode/tests/vcs/test_utils.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_utils.py @@ -0,0 +1,279 @@ +from __future__ import with_statement + +import os +import mock +import time +import shutil +import tempfile +import datetime +from rhodecode.lib.vcs.utils.compat import unittest +from rhodecode.lib.vcs.utils.paths import get_dirs_for_path +from rhodecode.lib.vcs.utils.helpers import get_dict_for_attrs +from rhodecode.lib.vcs.utils.helpers import get_scm +from rhodecode.lib.vcs.utils.helpers import get_scms_for_path +from rhodecode.lib.vcs.utils.helpers import get_total_seconds +from rhodecode.lib.vcs.utils.helpers import parse_changesets +from rhodecode.lib.vcs.utils.helpers import parse_datetime +from rhodecode.lib.vcs.utils import author_email, author_name +from rhodecode.lib.vcs.utils.paths import get_user_home +from rhodecode.lib.vcs.exceptions import VCSError + +from conf import TEST_HG_REPO, TEST_GIT_REPO, TEST_TMP_PATH + + +class PathsTest(unittest.TestCase): + + def _test_get_dirs_for_path(self, path, expected): + """ + Tests if get_dirs_for_path returns same as expected. + """ + expected = sorted(expected) + result = sorted(get_dirs_for_path(path)) + self.assertEqual(result, expected, + msg="%s != %s which was expected result for path %s" + % (result, expected, path)) + + def test_get_dirs_for_path(self): + path = 'foo/bar/baz/file' + paths_and_results = ( + ('foo/bar/baz/file', ['foo', 'foo/bar', 'foo/bar/baz']), + ('foo/bar/', ['foo', 'foo/bar']), + ('foo/bar', ['foo']), + ) + for path, expected in paths_and_results: + self._test_get_dirs_for_path(path, expected) + + + def test_get_scm(self): + self.assertEqual(('hg', TEST_HG_REPO), get_scm(TEST_HG_REPO)) + self.assertEqual(('git', TEST_GIT_REPO), get_scm(TEST_GIT_REPO)) + + def test_get_two_scms_for_path(self): + multialias_repo_path = os.path.join(TEST_TMP_PATH, 'hg-git-repo-2') + if os.path.isdir(multialias_repo_path): + shutil.rmtree(multialias_repo_path) + + os.mkdir(multialias_repo_path) + + self.assertRaises(VCSError, get_scm, multialias_repo_path) + + def test_get_scm_error_path(self): + self.assertRaises(VCSError, get_scm, 'err') + + def test_get_scms_for_path(self): + dirpath = tempfile.gettempdir() + new = os.path.join(dirpath, 'vcs-scms-for-path-%s' % time.time()) + os.mkdir(new) + self.assertEqual(get_scms_for_path(new), []) + + os.mkdir(os.path.join(new, '.tux')) + self.assertEqual(get_scms_for_path(new), []) + + os.mkdir(os.path.join(new, '.git')) + self.assertEqual(set(get_scms_for_path(new)), set(['git'])) + + os.mkdir(os.path.join(new, '.hg')) + self.assertEqual(set(get_scms_for_path(new)), set(['git', 'hg'])) + + +class TestParseChangesets(unittest.TestCase): + + def test_main_is_returned_correctly(self): + self.assertEqual(parse_changesets('123456'), { + 'start': None, + 'main': '123456', + 'end': None, + }) + + def test_start_is_returned_correctly(self): + self.assertEqual(parse_changesets('aaabbb..'), { + 'start': 'aaabbb', + 'main': None, + 'end': None, + }) + + def test_end_is_returned_correctly(self): + self.assertEqual(parse_changesets('..cccddd'), { + 'start': None, + 'main': None, + 'end': 'cccddd', + }) + + def test_that_two_or_three_dots_are_allowed(self): + text1 = 'a..b' + text2 = 'a...b' + self.assertEqual(parse_changesets(text1), parse_changesets(text2)) + + def test_that_input_is_stripped_first(self): + text1 = 'a..bb' + text2 = ' a..bb\t\n\t ' + self.assertEqual(parse_changesets(text1), parse_changesets(text2)) + + def test_that_exception_is_raised(self): + text = '123456.789012' # single dot is not recognized + with self.assertRaises(ValueError): + parse_changesets(text) + + def test_non_alphanumeric_raises_exception(self): + with self.assertRaises(ValueError): + parse_changesets('aaa@bbb') + + +class TestParseDatetime(unittest.TestCase): + + def test_datetime_text(self): + self.assertEqual(parse_datetime('2010-04-07 21:29:41'), + datetime.datetime(2010, 4, 7, 21, 29, 41)) + + def test_no_seconds(self): + self.assertEqual(parse_datetime('2010-04-07 21:29'), + datetime.datetime(2010, 4, 7, 21, 29)) + + def test_date_only(self): + self.assertEqual(parse_datetime('2010-04-07'), + datetime.datetime(2010, 4, 7)) + + def test_another_format(self): + self.assertEqual(parse_datetime('04/07/10 21:29:41'), + datetime.datetime(2010, 4, 7, 21, 29, 41)) + + def test_now(self): + self.assertTrue(parse_datetime('now') - datetime.datetime.now() < + datetime.timedelta(seconds=1)) + + def test_today(self): + today = datetime.date.today() + self.assertEqual(parse_datetime('today'), + datetime.datetime(*today.timetuple()[:3])) + + def test_yesterday(self): + yesterday = datetime.date.today() - datetime.timedelta(days=1) + self.assertEqual(parse_datetime('yesterday'), + datetime.datetime(*yesterday.timetuple()[:3])) + + def test_tomorrow(self): + tomorrow = datetime.date.today() + datetime.timedelta(days=1) + args = tomorrow.timetuple()[:3] + (23, 59, 59) + self.assertEqual(parse_datetime('tomorrow'), datetime.datetime(*args)) + + def test_days(self): + timestamp = datetime.datetime.today() - datetime.timedelta(days=3) + args = timestamp.timetuple()[:3] + (0, 0, 0, 0) + expected = datetime.datetime(*args) + self.assertEqual(parse_datetime('3d'), expected) + self.assertEqual(parse_datetime('3 d'), expected) + self.assertEqual(parse_datetime('3 day'), expected) + self.assertEqual(parse_datetime('3 days'), expected) + + def test_weeks(self): + timestamp = datetime.datetime.today() - datetime.timedelta(days=3 * 7) + args = timestamp.timetuple()[:3] + (0, 0, 0, 0) + expected = datetime.datetime(*args) + self.assertEqual(parse_datetime('3w'), expected) + self.assertEqual(parse_datetime('3 w'), expected) + self.assertEqual(parse_datetime('3 week'), expected) + self.assertEqual(parse_datetime('3 weeks'), expected) + + def test_mixed(self): + timestamp = datetime.datetime.today() - datetime.timedelta(days=2 * 7 + 3) + args = timestamp.timetuple()[:3] + (0, 0, 0, 0) + expected = datetime.datetime(*args) + self.assertEqual(parse_datetime('2w3d'), expected) + self.assertEqual(parse_datetime('2w 3d'), expected) + self.assertEqual(parse_datetime('2w 3 days'), expected) + self.assertEqual(parse_datetime('2 weeks 3 days'), expected) + + +class TestAuthorExtractors(unittest.TestCase): + TEST_AUTHORS = [('Marcin Kuzminski ', + ('Marcin Kuzminski', 'marcin@python-works.com')), + ('Marcin Kuzminski Spaces < marcin@python-works.com >', + ('Marcin Kuzminski Spaces', 'marcin@python-works.com')), + ('Marcin Kuzminski ', + ('Marcin Kuzminski', 'marcin.kuzminski@python-works.com')), + ('mrf RFC_SPEC ', + ('mrf RFC_SPEC', 'marcin+kuzminski@python-works.com')), + ('username ', + ('username', 'user@email.com')), + ('username ', + ('', 'justemail@mail.com')), + ('justname', + ('justname', '')), + ('Mr Double Name withemail@email.com ', + ('Mr Double Name', 'withemail@email.com')), + ] + + def test_author_email(self): + + for test_str, result in self.TEST_AUTHORS: + self.assertEqual(result[1], author_email(test_str)) + + + def test_author_name(self): + + for test_str, result in self.TEST_AUTHORS: + self.assertEqual(result[0], author_name(test_str)) + + +class TestGetDictForAttrs(unittest.TestCase): + + def test_returned_dict_has_expected_attrs(self): + obj = mock.Mock() + obj.NOT_INCLUDED = 'this key/value should not be included' + obj.CONST = True + obj.foo = 'aaa' + obj.attrs = {'foo': 'bar'} + obj.date = datetime.datetime(2010, 12, 31) + obj.count = 1001 + + self.assertEqual(get_dict_for_attrs(obj, ['CONST', 'foo', 'attrs', + 'date', 'count']), { + 'CONST': True, + 'foo': 'aaa', + 'attrs': {'foo': 'bar'}, + 'date': datetime.datetime(2010, 12, 31), + 'count': 1001, + }) + + +class TestGetTotalSeconds(unittest.TestCase): + + def assertTotalSecondsEqual(self, timedelta, expected_seconds): + result = get_total_seconds(timedelta) + self.assertEqual(result, expected_seconds, + "We computed %s seconds for %s but expected %s" + % (result, timedelta, expected_seconds)) + + def test_get_total_seconds_returns_proper_value(self): + self.assertTotalSecondsEqual(datetime.timedelta(seconds=1001), 1001) + + def test_get_total_seconds_returns_proper_value_for_partial_seconds(self): + self.assertTotalSecondsEqual(datetime.timedelta(seconds=50.65), 50.65) + + +class TestGetUserHome(unittest.TestCase): + + @mock.patch.object(os, 'environ', {}) + def test_defaults_to_none(self): + self.assertEqual(get_user_home(), '') + + @mock.patch.object(os, 'environ', {'HOME': '/home/foobar'}) + def test_unix_like(self): + self.assertEqual(get_user_home(), '/home/foobar') + + @mock.patch.object(os, 'environ', {'USERPROFILE': '/Users/foobar'}) + def test_windows_like(self): + self.assertEqual(get_user_home(), '/Users/foobar') + + @mock.patch.object(os, 'environ', {'HOME': '/home/foobar', + 'USERPROFILE': '/Users/foobar'}) + def test_prefers_home_over_userprofile(self): + self.assertEqual(get_user_home(), '/home/foobar') + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_utils_filesize.py b/rhodecode/tests/vcs/test_utils_filesize.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_utils_filesize.py @@ -0,0 +1,26 @@ +from __future__ import with_statement + +from rhodecode.lib.vcs.utils.filesize import filesizeformat +from rhodecode.lib.vcs.utils.compat import unittest + + +class TestFilesizeformat(unittest.TestCase): + + def test_bytes(self): + self.assertEqual(filesizeformat(10), '10 B') + + def test_kilobytes(self): + self.assertEqual(filesizeformat(1024 * 2), '2 KB') + + def test_megabytes(self): + self.assertEqual(filesizeformat(1024 * 1024 * 2.3), '2.3 MB') + + def test_gigabytes(self): + self.assertEqual(filesizeformat(1024 * 1024 * 1024 * 12.92), '12.92 GB') + + def test_that_function_respects_sep_paramtere(self): + self.assertEqual(filesizeformat(1, ''), '1B') + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/test_vcs.py b/rhodecode/tests/vcs/test_vcs.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_vcs.py @@ -0,0 +1,84 @@ +from __future__ import with_statement + +from rhodecode.lib.vcs import VCSError, get_repo, get_backend +from rhodecode.lib.vcs.backends.hg import MercurialRepository +from rhodecode.lib.vcs.utils.compat import unittest +from conf import TEST_HG_REPO, TEST_GIT_REPO, TEST_TMP_PATH +import os +import shutil + + +class VCSTest(unittest.TestCase): + """ + Tests for main module's methods. + """ + + def test_get_backend(self): + hg = get_backend('hg') + self.assertEqual(hg, MercurialRepository) + + def test_alias_detect_hg(self): + alias = 'hg' + path = TEST_HG_REPO + backend = get_backend(alias) + repo = backend(path) + self.assertEqual('hg',repo.alias) + + def test_alias_detect_git(self): + alias = 'git' + path = TEST_GIT_REPO + backend = get_backend(alias) + repo = backend(path) + self.assertEqual('git',repo.alias) + + def test_wrong_alias(self): + alias = 'wrong_alias' + self.assertRaises(VCSError, get_backend, alias) + + def test_get_repo(self): + alias = 'hg' + path = TEST_HG_REPO + backend = get_backend(alias) + repo = backend(path) + + self.assertEqual(repo.__class__, get_repo(path, alias).__class__) + self.assertEqual(repo.path, get_repo(path, alias).path) + + def test_get_repo_autoalias_hg(self): + alias = 'hg' + path = TEST_HG_REPO + backend = get_backend(alias) + repo = backend(path) + + self.assertEqual(repo.__class__, get_repo(path).__class__) + self.assertEqual(repo.path, get_repo(path).path) + + def test_get_repo_autoalias_git(self): + alias = 'git' + path = TEST_GIT_REPO + backend = get_backend(alias) + repo = backend(path) + + self.assertEqual(repo.__class__, get_repo(path).__class__) + self.assertEqual(repo.path, get_repo(path).path) + + + def test_get_repo_err(self): + blank_repo_path = os.path.join(TEST_TMP_PATH, 'blank-error-repo') + if os.path.isdir(blank_repo_path): + shutil.rmtree(blank_repo_path) + + os.mkdir(blank_repo_path) + self.assertRaises(VCSError, get_repo, blank_repo_path) + self.assertRaises(VCSError, get_repo, blank_repo_path + 'non_existing') + + def test_get_repo_multialias(self): + multialias_repo_path = os.path.join(TEST_TMP_PATH, 'hg-git-repo') + if os.path.isdir(multialias_repo_path): + shutil.rmtree(multialias_repo_path) + + os.mkdir(multialias_repo_path) + + os.mkdir(os.path.join(multialias_repo_path, '.git')) + os.mkdir(os.path.join(multialias_repo_path, '.hg')) + self.assertRaises(VCSError, get_repo, multialias_repo_path) diff --git a/rhodecode/tests/vcs/test_workdirs.py b/rhodecode/tests/vcs/test_workdirs.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_workdirs.py @@ -0,0 +1,90 @@ +from __future__ import with_statement + +import datetime +from rhodecode.lib.vcs.nodes import FileNode +from rhodecode.lib.vcs.utils.compat import unittest +from base import BackendTestMixin +from conf import SCM_TESTS + + +class WorkdirTestCaseMixin(BackendTestMixin): + + @classmethod + def _get_commits(cls): + commits = [ + { + 'message': u'Initial commit', + 'author': u'Joe Doe ', + 'date': datetime.datetime(2010, 1, 1, 20), + 'added': [ + FileNode('foobar', content='Foobar'), + FileNode('foobar2', content='Foobar II'), + FileNode('foo/bar/baz', content='baz here!'), + ], + }, + { + 'message': u'Changes...', + 'author': u'Jane Doe ', + 'date': datetime.datetime(2010, 1, 1, 21), + 'added': [ + FileNode('some/new.txt', content='news...'), + ], + 'changed': [ + FileNode('foobar', 'Foobar I'), + ], + 'removed': [], + }, + ] + return commits + + def test_get_branch_for_default_branch(self): + self.assertEqual(self.repo.workdir.get_branch(), + self.repo.DEFAULT_BRANCH_NAME) + + def test_get_branch_after_adding_one(self): + self.imc.add(FileNode('docs/index.txt', + content='Documentation\n')) + self.imc.commit( + message=u'New branch: foobar', + author=u'joe', + branch='foobar', + ) + + def test_get_changeset(self): + self.imc.add(FileNode('docs/index.txt', + content='Documentation\n')) + head = self.imc.commit( + message=u'New branch: foobar', + author=u'joe', + branch='foobar', + ) + self.assertEqual(self.repo.workdir.get_changeset(), head) + + def test_checkout_branch(self): + from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError + # first, 'foobranch' does not exist. + self.assertRaises(BranchDoesNotExistError, self.repo.workdir.checkout_branch, + branch='foobranch') + # create new branch 'foobranch'. + self.imc.add(FileNode('file1', content='blah')) + self.imc.commit(message=u'asd', author=u'john', branch='foobranch') + # go back to the default branch + self.repo.workdir.checkout_branch() + self.assertEqual(self.repo.workdir.get_branch(), self.backend_class.DEFAULT_BRANCH_NAME) + # checkout 'foobranch' + self.repo.workdir.checkout_branch('foobranch') + self.assertEqual(self.repo.workdir.get_branch(), 'foobranch') + + +# For each backend create test case class +for alias in SCM_TESTS: + attrs = { + 'backend_alias': alias, + } + cls_name = ''.join(('%s branch test' % alias).title().split()) + bases = (WorkdirTestCaseMixin, unittest.TestCase) + globals()[cls_name] = type(cls_name, bases, attrs) + + +if __name__ == '__main__': + unittest.main() diff --git a/rhodecode/tests/vcs/utils.py b/rhodecode/tests/vcs/utils.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/utils.py @@ -0,0 +1,97 @@ +""" +Utilities for tests only. These are not or should not be used normally - +functions here are crafted as we don't want to use ``vcs`` to verify tests. +""" +import os +import re +import sys + +from subprocess import Popen + + +class VCSTestError(Exception): + pass + + +def run_command(cmd, args): + """ + Runs command on the system with given ``args``. + """ + command = ' '.join((cmd, args)) + p = Popen(command, shell=True) + status = os.waitpid(p.pid, 0)[1] + return status + + +def eprint(msg): + """ + Prints given ``msg`` into sys.stderr as nose test runner hides all output + from sys.stdout by default and if we want to pipe stream somewhere we don't + need those verbose messages anyway. + Appends line break. + """ + sys.stderr.write(msg) + sys.stderr.write('\n') + + +class SCMFetcher(object): + + def __init__(self, alias, test_repo_path, remote_repo, clone_cmd): + """ + :param clone_cmd: command which would clone remote repository; pass + only first bits - remote path and destination would be appended + using ``remote_repo`` and ``test_repo_path`` + """ + self.alias = alias + self.test_repo_path = test_repo_path + self.remote_repo = remote_repo + self.clone_cmd = clone_cmd + + def setup(self): + if not os.path.isdir(self.test_repo_path): + self.fetch_repo() + + def fetch_repo(self): + """ + Tries to fetch repository from remote path. + """ + remote = self.remote_repo + eprint("Fetching repository %s into %s" % (remote, self.test_repo_path)) + run_command(self.clone_cmd, '%s %s' % (remote, self.test_repo_path)) + + +def get_normalized_path(path): + """ + If given path exists, new path would be generated and returned. Otherwise + same whats given is returned. Assumes that there would be no more than + 10000 same named files. + """ + if os.path.exists(path): + dir, basename = os.path.split(path) + splitted_name = basename.split('.') + if len(splitted_name) > 1: + ext = splitted_name[-1] + else: + ext = None + name = '.'.join(splitted_name[:-1]) + matcher = re.compile(r'^.*-(\d{5})$') + start = 0 + m = matcher.match(name) + if not m: + # Haven't append number yet so return first + newname = '%s-00000' % name + newpath = os.path.join(dir, newname) + if ext: + newpath = '.'.join((newpath, ext)) + return get_normalized_path(newpath) + else: + start = int(m.group(1)[-5:]) + 1 + for x in xrange(start, 10000): + newname = name[:-5] + str(x).rjust(5, '0') + newpath = os.path.join(dir, newname) + if ext: + newpath = '.'.join((newpath, ext)) + if not os.path.exists(newpath): + return newpath + raise VCSTestError("Couldn't compute new path for %s" % path) + return path diff --git a/rhodecode/tests/vcs_test_git.tar.gz b/rhodecode/tests/vcs_test_git.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..5f6dbf4cfc6c3e741711ccf7508490aa95d66966 GIT binary patch literal 1135049 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

    $o0XC%-_ZfV{qqJG^bBYm9}jf_g5T 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

    2H(fri-#Q0x=2vl$RqJj@R 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

    + %if c.visual.stylify_metatags: + ${h.urlify_text(h.desc_stylize(h.truncate(repo['description'],60)))} + %else: ${h.truncate(repo['description'],60)} + %endif - ${h.age(repo['last_change'])} + ${h.age(repo['last_change'])} @@ -119,11 +127,13 @@ -
    ${c.journal_data}
    - +
    + +
    +
    ${_('Public Journal')}
    + +
    + +
    ${c.journal_data}
    +
    diff --git a/rhodecode/templates/login.html b/rhodecode/templates/login.html --- a/rhodecode/templates/login.html +++ b/rhodecode/templates/login.html @@ -51,7 +51,7 @@
    - ${h.submit('sign_in',_('Sign In'),class_="ui-button")} + ${h.submit('sign_in',_('Sign In'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/password_reset.html b/rhodecode/templates/password_reset.html --- a/rhodecode/templates/password_reset.html +++ b/rhodecode/templates/password_reset.html @@ -27,7 +27,7 @@
    - ${h.submit('send',_('Reset my password'),class_="ui-button")} + ${h.submit('send',_('Reset my password'),class_="ui-btn large")}
    ${_('Password reset link will be send to matching email address')}
    diff --git a/rhodecode/templates/pullrequests/pullrequest.html b/rhodecode/templates/pullrequests/pullrequest.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/pullrequests/pullrequest.html @@ -0,0 +1,189 @@ +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${c.repo_name} ${_('New pull request')} + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_(u'Home'),h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))} + » + ${_('New pull request')} + + +<%def name="main()"> + +
    + +
    + ${self.breadcrumbs()} +
    + ${h.form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')} +
    + + ##ORG +
    +
    +
    + gravatar +
    + + ${h.select('org_repo','',c.org_repos,class_='refs')}:${h.select('org_ref','',c.org_refs,class_='refs')} + +
    ${c.rhodecode_db_repo.description}
    +
    +
    +
    +
    + +
    + + ##OTHER, most Probably the PARENT OF THIS FORK +
    +
    +
    + gravatar +
    + + ${h.select('other_repo',c.default_pull_request ,c.other_repos,class_='refs')}:${h.select('other_ref','',c.default_revs,class_='refs')} + +
    +
    +
    +
    +
    + ## overview pulled by ajax +
    + +
    +
    +

    ${_('Pull request reviewers')}

    +
    + ## members goes here ! +
    +
      + %for member in c.review_members: +
    • +
      +
      gravatar
      +
      ${member.full_name} (${_('owner')})
      + + +
      +
    • + %endfor +
    +
    + +
    +
    + ${h.text('user', class_='yui-ac-input')} + ${_('Add reviewer to this pull request.')} +
    +
    +
    +
    +
    +

    ${_('Create new pull request')}

    + +
    + + +
    + +
    +
    + +
    +
    + ${h.text('pullrequest_title',size=30)} +
    +
    + +
    +
    + +
    +
    + ${h.textarea('pullrequest_desc',size=30)} +
    +
    + +
    + ${h.submit('save',_('Send pull request'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")} +
    +
    +
    + ${h.end_form()} + +
    + + + + diff --git a/rhodecode/templates/pullrequests/pullrequest_show.html b/rhodecode/templates/pullrequests/pullrequest_show.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/pullrequests/pullrequest_show.html @@ -0,0 +1,195 @@ +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${c.repo_name} ${_('Pull request #%s') % c.pull_request.pull_request_id} + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_(u'Home'),h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))} + » + ${_('Pull request #%s') % c.pull_request.pull_request_id} + + +<%def name="main()"> + +
    + +
    + ${self.breadcrumbs()} +
    + %if c.pull_request.is_closed(): +
    ${_('Closed %s') % (h.age(c.pull_request.updated_on))}
    + %endif +

    ${_('Title')}: ${c.pull_request.title}

    + +
    +
    +
    +
    + +
    +
    +
    + %if c.current_changeset_status: +
    [${h.changeset_status_lbl(c.current_changeset_status)}]
    +
    + %endif +
    +
    +
    +
    +
    + +
    +
    +
    ${ungettext('%d reviewer', '%d reviewers',len(c.pull_request_pending_reviewers)) % len(c.pull_request_pending_reviewers)}
    +
    +
    +
    +
    +
    ${h.literal(c.pull_request.description)}
    +
    +
    ${_('Created on')}: ${h.fmt_date(c.pull_request.created_on)}
    +
    + +
    + ##DIFF +
    +
    +
    ${_('Compare view')}
    +
    +
    + ##CS +
    ${_('Incoming changesets')}
    + <%include file="/compare/compare_cs.html" /> + + ## FILES +
    ${_('Files affected')}
    +
    + %for fid, change, f, stat in c.files: +
    +
    ${h.link_to(h.safe_unicode(f),h.url.current(anchor=fid))}
    +
    ${h.fancy_file_stats(stat)}
    +
    + %endfor +
    +
    +
    + ## REVIEWERS +
    +

    ${_('Pull request reviewers')}

    +
    + ## members goes here ! +
    +
      + %for member,status in c.pull_request_reviewers: +
    • +
      +
      + +
      +
      gravatar
      +
      ${member.full_name} (${_('owner')})
      + + %if not c.pull_request.is_closed() and (h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id): + + %endif +
      +
    • + %endfor +
    +
    + %if not c.pull_request.is_closed(): +
    + %if h.HasPermissionAny('hg.admin', 'repository.admin')() or c.pull_request.author.user_id == c.rhodecode_user.user_id: +
    + ${h.text('user', class_='yui-ac-input')} + ${_('Add reviewer to this pull request.')} +
    +
    +
    + ${_('save')} +
    + %endif +
    + %endif +
    +
    +
    + + + ## diff block + <%namespace name="diff_block" file="/changeset/diff_block.html"/> + %for fid, change, f, stat in c.files: + ${diff_block.diff_block_simple([c.changes[fid]])} + %endfor + + ## template for inline comment form + <%namespace name="comment" file="/changeset/changeset_file_comment.html"/> + ${comment.comment_inline_form()} + + ## render comments and inlines + ${comment.generate_comments()} + + % if not c.pull_request.is_closed(): + ## main comment form and it status + ${comment.comments(h.url('pullrequest_comment', repo_name=c.repo_name, + pull_request_id=c.pull_request.pull_request_id), + c.current_changeset_status, + close_btn=True)} + %endif + + + +
    + + diff --git a/rhodecode/templates/pullrequests/pullrequest_show_all.html b/rhodecode/templates/pullrequests/pullrequest_show_all.html new file mode 100644 --- /dev/null +++ b/rhodecode/templates/pullrequests/pullrequest_show_all.html @@ -0,0 +1,42 @@ +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${c.repo_name} ${_('all pull requests')} + + +<%def name="breadcrumbs_links()"> + ${h.link_to(_(u'Home'),h.url('/'))} + » + ${h.link_to(c.repo_name,h.url('changelog_home',repo_name=c.repo_name))} + » + ${_('All pull requests')} + + +<%def name="main()"> + +
    + +
    + ${self.breadcrumbs()} +
    + + %for pr in c.pull_requests: +
    +

    + %if pr.is_closed(): + ${_('Closed')} + %endif + + ${_('Pull request #%s opened by %s on %s') % (pr.pull_request_id, pr.author.full_name, h.fmt_date(pr.created_on))} + +

    +
    ${_('Title')}: ${pr.title}
    +
    ${pr.description}
    +
    + %endfor + +
    + + + + diff --git a/rhodecode/templates/register.html b/rhodecode/templates/register.html --- a/rhodecode/templates/register.html +++ b/rhodecode/templates/register.html @@ -44,10 +44,10 @@
    - +
    - ${h.text('name',class_="medium")} + ${h.text('firstname',class_="medium")}
    @@ -71,7 +71,7 @@
    - ${h.submit('sign_up',_('Sign Up'),class_="ui-button")} + ${h.submit('sign_up',_('Sign Up'),class_="ui-btn large")} %if c.auto_active:
    ${_('Your account will be activated right after registration')}
    %else: diff --git a/rhodecode/templates/repo_switcher_list.html b/rhodecode/templates/repo_switcher_list.html --- a/rhodecode/templates/repo_switcher_list.html +++ b/rhodecode/templates/repo_switcher_list.html @@ -1,17 +1,17 @@ ## -*- coding: utf-8 -*-
  • - +
  • %for repo in c.repos_list: - %if repo['dbrepo']['private']: + %if repo['dbrepo']['private'] and c.visual.show_private_icon:
  • ${_('Private repository')} ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])}
  • - %else: + %elif not repo['dbrepo']['private'] and c.visual.show_public_icon:
  • ${_('Public repository')} ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="repo_name %s" % repo['dbrepo']['repo_type'])} diff --git a/rhodecode/templates/search/search.html b/rhodecode/templates/search/search.html --- a/rhodecode/templates/search/search.html +++ b/rhodecode/templates/search/search.html @@ -1,13 +1,19 @@ ## -*- coding: utf-8 -*- <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Search')} - ${'"%s"' % c.cur_query if c.cur_query else None} + %if c.cur_query: %if c.repo_name: - ${_('in repository: ') + c.repo_name} + ${_('Search "%s" in repository: %s') % (c.cur_query, c.repo_name)} %else: - ${_('in all repositories')} + ${_('Search "%s" in all repositories') % c.cur_query} %endif + %else: + %if c.repo_name: + ${_('Search in repository: %s') % c.repo_name} + %else: + ${_('Search in all repositories')} + %endif + %endif - ${c.rhodecode_name} <%def name="breadcrumbs()"> @@ -21,11 +27,11 @@
    -
    ${_('Search')} +
    %if c.repo_name: - ${_('in repository: ') + c.repo_name} + ${_('Search in repository: %s') % c.repo_name} %else: - ${_('in all repositories')} + ${_('Search in all repositories')} %endif
    @@ -55,7 +61,7 @@
    ${h.select('type',c.cur_type,[('content',_('File contents')), - ##('commit',_('Commit messages')), + ('commit',_('Commit messages')), ('path',_('File names')), ##('repository',_('Repository names')), ])} @@ -65,16 +71,17 @@
  • ${h.end_form()} - - %if c.cur_search == 'content': +
    diff --git a/rhodecode/templates/search/search_commit.html b/rhodecode/templates/search/search_commit.html --- a/rhodecode/templates/search/search_commit.html +++ b/rhodecode/templates/search/search_commit.html @@ -0,0 +1,45 @@ +##commit highligthing + +%for cnt,sr in enumerate(c.formated_results): + %if h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(sr['repository'],'search results check'): +
    +
    +
    +
    ${h.link_to(h.literal('%s » %s' % (sr['repository'],sr['raw_id'])), + h.url('changeset_home',repo_name=sr['repository'],revision=sr['raw_id']))} + ${h.fmt_date(h.time_to_datetime(sr['date']))} +
    +
    +
    +
    +
    + gravatar +
    + ${h.person(sr['author'])}
    + ${h.email_or_none(sr['author'])}
    +
    + %if sr['message_hl']: +
    +
    ${h.literal(sr['message_hl'])}
    +
    + %else: +
    ${h.urlify_commit(sr['message'], sr['repository'])}
    + %endif +
    +
    +
    + %else: + %if cnt == 0: +
    +
    +
    ${_('Permission denied')}
    +
    +
    + %endif + %endif +%endfor +%if c.cur_query and c.formated_results: +
    + ${c.formated_results.pager('$link_previous ~2~ $link_next')} +
    +%endif diff --git a/rhodecode/templates/settings/repo_settings.html b/rhodecode/templates/settings/repo_settings.html --- a/rhodecode/templates/settings/repo_settings.html +++ b/rhodecode/templates/settings/repo_settings.html @@ -2,11 +2,11 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Settings')} - ${c.rhodecode_name} + ${_('%s Settings') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))} » @@ -53,6 +53,15 @@
    +
    + +
    +
    + ${h.select('landing_rev','',c.landing_revs,class_="medium")} + ${_('Default revision for files page, downloads, whoosh and readme')} +
    +
    +
    @@ -81,8 +90,8 @@
    - ${h.submit('save','Save',class_="ui-button")} - ${h.reset('reset','Reset',class_="ui-button")} + ${h.submit('save',_('Save'),class_="ui-btn large")} + ${h.reset('reset',_('Reset'),class_="ui-btn large")}
    diff --git a/rhodecode/templates/shortlog/shortlog.html b/rhodecode/templates/shortlog/shortlog.html --- a/rhodecode/templates/shortlog/shortlog.html +++ b/rhodecode/templates/shortlog/shortlog.html @@ -2,12 +2,12 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${c.repo_name} ${_('Shortlog')} - ${c.rhodecode_name} + ${_('%s Shortlog') % c.repo_name} - ${c.rhodecode_name} <%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} + ${h.link_to(_(u'Home'),h.url('/'))} » ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} » diff --git a/rhodecode/templates/shortlog/shortlog_data.html b/rhodecode/templates/shortlog/shortlog_data.html --- a/rhodecode/templates/shortlog/shortlog_data.html +++ b/rhodecode/templates/shortlog/shortlog_data.html @@ -19,7 +19,7 @@ h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id), title=cs.message)}
    + ${h.age(cs.date)} ${h.person(cs.author)} ${tag[1].date}${h.fmt_date(tag[1].date)} ${h.person(tag[1].author)}
    diff --git a/rhodecode/tests/__init__.py b/rhodecode/tests/__init__.py --- a/rhodecode/tests/__init__.py +++ b/rhodecode/tests/__init__.py @@ -10,6 +10,9 @@ setup-app`) and provides the base testin import os import time import logging +import datetime +import hashlib +import tempfile from os.path import join as jn from unittest import TestCase @@ -24,9 +27,11 @@ from webtest import TestApp from rhodecode import is_windows from rhodecode.model.meta import Session from rhodecode.model.db import User +from rhodecode.tests.nose_parametrized import parameterized import pylons.test + os.environ['TZ'] = 'UTC' if not is_windows: time.tzset() @@ -34,11 +39,15 @@ if not is_windows: log = logging.getLogger(__name__) __all__ = [ - 'environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO', - 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK', - 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS', + 'parameterized', 'environ', 'url', 'get_new_dir', 'TestController', + 'TESTS_TMP_PATH', 'HG_REPO', 'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', + 'HG_FORK', 'GIT_FORK', 'TEST_USER_ADMIN_LOGIN', 'TEST_USER_ADMIN_PASS', + 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS', 'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN', - 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL' + 'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL', 'TEST_HG_REPO', + 'TEST_HG_REPO_CLONE', 'TEST_HG_REPO_PULL', 'TEST_GIT_REPO', + 'TEST_GIT_REPO_CLONE', 'TEST_GIT_REPO_PULL', 'HG_REMOTE_REPO', + 'GIT_REMOTE_REPO', 'SCM_TESTS', ] # Invoke websetup with the current config file @@ -47,6 +56,7 @@ log = logging.getLogger(__name__) ##RUNNING DESIRED TESTS # nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account # nosetests --pdb --pdb-failures +# nosetests --with-coverage --cover-package=rhodecode.model.validators rhodecode.tests.test_validators environ = {} #SOME GLOBALS FOR TESTS @@ -73,6 +83,45 @@ NEW_GIT_REPO = 'vcs_test_git_new' HG_FORK = 'vcs_test_hg_fork' GIT_FORK = 'vcs_test_git_fork' +## VCS +SCM_TESTS = ['hg', 'git'] +uniq_suffix = str(int(time.mktime(datetime.datetime.now().timetuple()))) + +GIT_REMOTE_REPO = 'git://github.com/codeinn/vcs.git' + +TEST_GIT_REPO = jn(TESTS_TMP_PATH, GIT_REPO) +TEST_GIT_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcsgitclone%s' % uniq_suffix) +TEST_GIT_REPO_PULL = jn(TESTS_TMP_PATH, 'vcsgitpull%s' % uniq_suffix) + + +HG_REMOTE_REPO = 'http://bitbucket.org/marcinkuzminski/vcs' + +TEST_HG_REPO = jn(TESTS_TMP_PATH, HG_REPO) +TEST_HG_REPO_CLONE = jn(TESTS_TMP_PATH, 'vcshgclone%s' % uniq_suffix) +TEST_HG_REPO_PULL = jn(TESTS_TMP_PATH, 'vcshgpull%s' % uniq_suffix) + +TEST_DIR = tempfile.gettempdir() +TEST_REPO_PREFIX = 'vcs-test' + +# cached repos if any ! +# comment out to get some other repos from bb or github +GIT_REMOTE_REPO = jn(TESTS_TMP_PATH, GIT_REPO) +HG_REMOTE_REPO = jn(TESTS_TMP_PATH, HG_REPO) + + +def get_new_dir(title): + """ + Returns always new directory path. + """ + from rhodecode.tests.vcs.utils import get_normalized_path + name = TEST_REPO_PREFIX + if title: + name = '-'.join((name, title)) + hex = hashlib.sha1(str(time.time())).hexdigest() + name = '-'.join((name, hex)) + path = os.path.join(TEST_DIR, name) + return get_normalized_path(path) + class TestController(TestCase): @@ -90,8 +139,8 @@ class TestController(TestCase): password=TEST_USER_ADMIN_PASS): self._logged_username = username response = self.app.post(url(controller='login', action='index'), - {'username':username, - 'password':password}) + {'username': username, + 'password': password}) if 'invalid user name' in response.body: self.fail('could not login using %s %s' % (username, password)) @@ -109,4 +158,8 @@ class TestController(TestCase): def checkSessionFlash(self, response, msg): self.assertTrue('flash' in response.session) - self.assertTrue(msg in response.session['flash'][0][1]) + if not msg in response.session['flash'][0][1]: + self.fail( + 'msg `%s` not found in session flash: got `%s` instead' % ( + msg, response.session['flash']) + ) diff --git a/rhodecode/tests/api/__init__.py b/rhodecode/tests/api/__init__.py new file mode 100644 diff --git a/rhodecode/tests/api/api_base.py b/rhodecode/tests/api/api_base.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/api/api_base.py @@ -0,0 +1,990 @@ +from __future__ import with_statement +import random +import mock + +from rhodecode.tests import * +from rhodecode.lib.compat import json +from rhodecode.lib.auth import AuthUser +from rhodecode.model.user import UserModel +from rhodecode.model.users_group import UsersGroupModel +from rhodecode.model.repo import RepoModel +from rhodecode.model.meta import Session +from rhodecode.model.scm import ScmModel +from rhodecode.model.db import Repository + +API_URL = '/_admin/api' + + +def _build_data(apikey, method, **kw): + """ + Builds API data with given random ID + + :param random_id: + :type random_id: + """ + random_id = random.randrange(1, 9999) + return random_id, json.dumps({ + "id": random_id, + "api_key": apikey, + "method": method, + "args": kw + }) + +jsonify = lambda obj: json.loads(json.dumps(obj)) + + +def crash(*args, **kwargs): + raise Exception('Total Crash !') + + +def api_call(test_obj, params): + response = test_obj.app.post(API_URL, content_type='application/json', + params=params) + return response + + +TEST_USERS_GROUP = 'test_users_group' + + +def make_users_group(name=TEST_USERS_GROUP): + gr = UsersGroupModel().create(name=name) + UsersGroupModel().add_user_to_group(users_group=gr, + user=TEST_USER_ADMIN_LOGIN) + Session().commit() + return gr + + +def destroy_users_group(name=TEST_USERS_GROUP): + UsersGroupModel().delete(users_group=name, force=True) + Session().commit() + + +def create_repo(repo_name, repo_type): + # create new repo + form_data = dict(repo_name=repo_name, + repo_name_full=repo_name, + fork_name=None, + description='description %s' % repo_name, + repo_group=None, + private=False, + repo_type=repo_type, + clone_uri=None, + landing_rev='tip') + cur_user = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) + r = RepoModel().create(form_data, cur_user) + Session().commit() + return r + + +def create_fork(fork_name, fork_type, fork_of): + fork = RepoModel(Session())._get_repo(fork_of) + r = create_repo(fork_name, fork_type) + r.fork = fork + Session().add(r) + Session().commit() + return r + + +def destroy_repo(repo_name): + RepoModel().delete(repo_name) + Session().commit() + + +class BaseTestApi(object): + REPO = None + REPO_TYPE = None + + @classmethod + 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( + username='test-api', + password='test', + email='test@api.rhodecode.org', + firstname='first', + lastname='last' + ) + Session().commit() + self.TEST_USER_LOGIN = self.TEST_USER.username + + @classmethod + def teardownClass(self): + pass + + def setUp(self): + self.maxDiff = None + make_users_group() + + def tearDown(self): + destroy_users_group() + + def _compare_ok(self, id_, expected, given): + expected = jsonify({ + 'id': id_, + 'error': None, + 'result': expected + }) + given = json.loads(given) + self.assertEqual(expected, given) + + def _compare_error(self, id_, expected, given): + expected = jsonify({ + 'id': id_, + 'error': expected, + 'result': None + }) + given = json.loads(given) + self.assertEqual(expected, given) + +# def test_Optional(self): +# from rhodecode.controllers.api.api import Optional +# option1 = Optional(None) +# self.assertEqual('' % None, repr(option1)) +# +# self.assertEqual(1, Optional.extract(Optional(1))) +# self.assertEqual('trololo', Optional.extract('trololo')) + + def test_api_wrong_key(self): + id_, params = _build_data('trololo', 'get_user') + response = api_call(self, params) + + expected = 'Invalid API KEY' + self._compare_error(id_, expected, given=response.body) + + def test_api_missing_non_optional_param(self): + id_, params = _build_data(self.apikey, 'get_user') + response = api_call(self, params) + + expected = 'Missing non optional `userid` arg in JSON DATA' + self._compare_error(id_, expected, given=response.body) + + def test_api_get_users(self): + id_, params = _build_data(self.apikey, 'get_users',) + response = api_call(self, params) + ret_all = [] + for usr in UserModel().get_all(): + ret = usr.get_api_data() + ret_all.append(jsonify(ret)) + expected = ret_all + self._compare_ok(id_, expected, given=response.body) + + def test_api_get_user(self): + id_, params = _build_data(self.apikey, 'get_user', + userid=TEST_USER_ADMIN_LOGIN) + 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_that_does_not_exist(self): + id_, params = _build_data(self.apikey, 'get_user', + userid='trololo') + response = api_call(self, params) + + expected = "user `%s` does not exist" % 'trololo' + self._compare_error(id_, expected, given=response.body) + + def test_api_pull(self): + #TODO: issues with rhodecode_extras here.. not sure why ! + pass + +# repo_name = 'test_pull' +# r = create_repo(repo_name, self.REPO_TYPE) +# r.clone_uri = TEST_self.REPO +# Session.add(r) +# Session.commit() +# +# id_, params = _build_data(self.apikey, 'pull', +# repoid=repo_name,) +# response = self.app.post(API_URL, content_type='application/json', +# params=params) +# +# expected = 'Pulled from `%s`' % repo_name +# self._compare_ok(id_, expected, given=response.body) +# +# destroy_repo(repo_name) + + def test_api_pull_error(self): + id_, params = _build_data(self.apikey, 'pull', + repoid=self.REPO,) + response = api_call(self, params) + + expected = 'Unable to pull changes from `%s`' % self.REPO + self._compare_error(id_, expected, given=response.body) + + def test_api_rescan_repos(self): + id_, params = _build_data(self.apikey, 'rescan_repos') + response = api_call(self, params) + + expected = {'added': [], 'removed': []} + self._compare_ok(id_, expected, given=response.body) + + @mock.patch.object(ScmModel, 'repo_scan', crash) + def test_api_rescann_error(self): + id_, params = _build_data(self.apikey, 'rescan_repos',) + response = api_call(self, params) + + expected = 'Error occurred during rescan repositories action' + self._compare_error(id_, expected, given=response.body) + + def test_api_lock_repo_lock_aquire(self): + id_, params = _build_data(self.apikey, 'lock', + userid=TEST_USER_ADMIN_LOGIN, + 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) + + def test_api_lock_repo_lock_release(self): + id_, params = _build_data(self.apikey, 'lock', + userid=TEST_USER_ADMIN_LOGIN, + repoid=self.REPO, + locked=False) + response = api_call(self, params) + expected = ('User `%s` set lock state for repo `%s` to `%s`' + % (TEST_USER_ADMIN_LOGIN, self.REPO, False)) + 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', + userid=TEST_USER_ADMIN_LOGIN, + repoid=self.REPO, + locked=True) + response = api_call(self, params) + + expected = 'Error occurred locking repository `%s`' % self.REPO + self._compare_error(id_, expected, given=response.body) + + def test_api_create_existing_user(self): + id_, params = _build_data(self.apikey, 'create_user', + username=TEST_USER_ADMIN_LOGIN, + email='test@foo.com', + password='trololo') + response = api_call(self, params) + + expected = "user `%s` already exist" % TEST_USER_ADMIN_LOGIN + self._compare_error(id_, expected, given=response.body) + + def test_api_create_user_with_existing_email(self): + id_, params = _build_data(self.apikey, 'create_user', + username=TEST_USER_ADMIN_LOGIN + 'new', + email=TEST_USER_REGULAR_EMAIL, + password='trololo') + response = api_call(self, params) + + expected = "email `%s` already exist" % TEST_USER_REGULAR_EMAIL + self._compare_error(id_, expected, given=response.body) + + def test_api_create_user(self): + username = 'test_new_api_user' + email = username + "@foo.com" + + id_, params = _build_data(self.apikey, 'create_user', + username=username, + email=email, + password='trololo') + response = api_call(self, params) + + usr = UserModel().get_by_username(username) + ret = dict( + msg='created new user `%s`' % username, + user=jsonify(usr.get_api_data()) + ) + + expected = ret + self._compare_ok(id_, expected, given=response.body) + + UserModel().delete(usr.user_id) + Session().commit() + + @mock.patch.object(UserModel, 'create_or_update', crash) + def test_api_create_user_when_exception_happened(self): + + username = 'test_new_api_user' + email = username + "@foo.com" + + id_, params = _build_data(self.apikey, 'create_user', + username=username, + email=email, + password='trololo') + response = api_call(self, params) + expected = 'failed to create user `%s`' % username + self._compare_error(id_, expected, given=response.body) + + def test_api_delete_user(self): + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', + email=u'u232@rhodecode.org', + firstname=u'u1', lastname=u'u1') + Session().commit() + username = usr.username + email = usr.email + usr_id = usr.user_id + ## DELETE THIS USER NOW + + id_, params = _build_data(self.apikey, 'delete_user', + userid=username,) + response = api_call(self, params) + + ret = {'msg': 'deleted user ID:%s %s' % (usr_id, username), + 'user': None} + expected = ret + self._compare_ok(id_, expected, given=response.body) + + @mock.patch.object(UserModel, 'delete', crash) + def test_api_delete_user_when_exception_happened(self): + usr = UserModel().create_or_update(username=u'test_user', + password=u'qweqwe', + email=u'u232@rhodecode.org', + firstname=u'u1', lastname=u'u1') + Session().commit() + username = usr.username + + id_, params = _build_data(self.apikey, 'delete_user', + userid=username,) + response = api_call(self, params) + ret = 'failed to delete ID:%s %s' % (usr.user_id, + usr.username) + expected = ret + self._compare_error(id_, expected, given=response.body) + + @parameterized.expand([('firstname', 'new_username'), + ('lastname', 'new_username'), + ('email', 'new_username'), + ('admin', True), + ('admin', False), + ('ldap_dn', 'test'), + ('ldap_dn', None), + ('active', False), + ('active', True), + ('password', 'newpass') + ]) + def test_api_update_user(self, name, expected): + usr = UserModel().get_by_username(self.TEST_USER_LOGIN) + kw = {name: expected, + 'userid': usr.user_id} + id_, params = _build_data(self.apikey, 'update_user', **kw) + response = api_call(self, params) + + ret = { + 'msg': 'updated user ID:%s %s' % (usr.user_id, self.TEST_USER_LOGIN), + 'user': jsonify(UserModel()\ + .get_by_username(self.TEST_USER_LOGIN)\ + .get_api_data()) + } + + expected = ret + self._compare_ok(id_, expected, given=response.body) + + def test_api_update_user_no_changed_params(self): + usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) + ret = jsonify(usr.get_api_data()) + id_, params = _build_data(self.apikey, 'update_user', + userid=TEST_USER_ADMIN_LOGIN) + + response = api_call(self, params) + ret = { + 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN), + 'user': ret + } + expected = ret + self._compare_ok(id_, expected, given=response.body) + + def test_api_update_user_by_user_id(self): + usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) + ret = jsonify(usr.get_api_data()) + id_, params = _build_data(self.apikey, 'update_user', + userid=usr.user_id) + + response = api_call(self, params) + ret = { + 'msg': 'updated user ID:%s %s' % (usr.user_id, TEST_USER_ADMIN_LOGIN), + 'user': ret + } + expected = ret + self._compare_ok(id_, expected, given=response.body) + + @mock.patch.object(UserModel, 'update_user', crash) + def test_api_update_user_when_exception_happens(self): + usr = UserModel().get_by_username(TEST_USER_ADMIN_LOGIN) + ret = jsonify(usr.get_api_data()) + id_, params = _build_data(self.apikey, 'update_user', + userid=usr.user_id) + + response = api_call(self, params) + ret = 'failed to update user `%s`' % usr.user_id + + expected = ret + self._compare_error(id_, expected, given=response.body) + + def test_api_get_repo(self): + new_group = 'some_new_group' + make_users_group(new_group) + RepoModel().grant_users_group_permission(repo=self.REPO, + group_name=new_group, + perm='repository.read') + Session().commit() + 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) + destroy_users_group(new_group) + + def test_api_get_repo_that_doesn_not_exist(self): + id_, params = _build_data(self.apikey, 'get_repo', + repoid='no-such-repo') + response = api_call(self, params) + + ret = 'repository `%s` does not exist' % 'no-such-repo' + expected = ret + self._compare_error(id_, expected, given=response.body) + + def test_api_get_repos(self): + id_, params = _build_data(self.apikey, 'get_repos') + response = api_call(self, params) + + result = [] + for repo in RepoModel().get_all(): + 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'), ]) + def test_api_get_repo_nodes(self, name, ret_type): + rev = 'tip' + path = '/' + id_, params = _build_data(self.apikey, 'get_repo_nodes', + repoid=self.REPO, revision=rev, + root_path=path, + ret_type=ret_type) + response = api_call(self, params) + + # we don't the actual return types here since it's tested somewhere + # else + expected = json.loads(response.body)['result'] + self._compare_ok(id_, expected, given=response.body) + + def test_api_get_repo_nodes_bad_revisions(self): + rev = 'i-dont-exist' + path = '/' + id_, params = _build_data(self.apikey, 'get_repo_nodes', + repoid=self.REPO, revision=rev, + root_path=path,) + response = api_call(self, params) + + expected = 'failed to get repo: `%s` nodes' % self.REPO + self._compare_error(id_, expected, given=response.body) + + def test_api_get_repo_nodes_bad_path(self): + rev = 'tip' + path = '/idontexits' + id_, params = _build_data(self.apikey, 'get_repo_nodes', + repoid=self.REPO, revision=rev, + root_path=path,) + response = api_call(self, params) + + expected = 'failed to get repo: `%s` nodes' % self.REPO + self._compare_error(id_, expected, given=response.body) + + def test_api_get_repo_nodes_bad_ret_type(self): + rev = 'tip' + path = '/' + ret_type = 'error' + id_, params = _build_data(self.apikey, 'get_repo_nodes', + repoid=self.REPO, revision=rev, + root_path=path, + ret_type=ret_type) + response = api_call(self, params) + + expected = 'ret_type must be one of %s' % (['files', 'dirs', 'all']) + self._compare_error(id_, expected, given=response.body) + + def test_api_create_repo(self): + repo_name = 'api-repo' + id_, params = _build_data(self.apikey, 'create_repo', + repo_name=repo_name, + owner=TEST_USER_ADMIN_LOGIN, + 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_unknown_owner(self): + repo_name = 'api-repo' + owner = 'i-dont-exist' + id_, params = _build_data(self.apikey, 'create_repo', + repo_name=repo_name, + owner=owner, + repo_type='hg', + ) + response = api_call(self, params) + expected = 'user `%s` does not exist' % owner + self._compare_error(id_, expected, given=response.body) + + def test_api_create_repo_exists(self): + repo_name = self.REPO + id_, params = _build_data(self.apikey, 'create_repo', + repo_name=repo_name, + owner=TEST_USER_ADMIN_LOGIN, + repo_type='hg', + ) + response = api_call(self, params) + expected = "repo `%s` already exist" % repo_name + self._compare_error(id_, expected, given=response.body) + + @mock.patch.object(RepoModel, 'create_repo', crash) + def test_api_create_repo_exception_occurred(self): + repo_name = 'api-repo' + id_, params = _build_data(self.apikey, 'create_repo', + repo_name=repo_name, + owner=TEST_USER_ADMIN_LOGIN, + repo_type='hg', + ) + response = api_call(self, params) + expected = 'failed to create repository `%s`' % repo_name + self._compare_error(id_, expected, given=response.body) + + def test_api_delete_repo(self): + repo_name = 'api_delete_me' + create_repo(repo_name, self.REPO_TYPE) + + id_, params = _build_data(self.apikey, 'delete_repo', + repoid=repo_name,) + response = api_call(self, params) + + ret = { + 'msg': 'Deleted repository `%s`' % repo_name, + 'success': True + } + expected = ret + self._compare_ok(id_, expected, given=response.body) + + def test_api_delete_repo_exception_occurred(self): + repo_name = 'api_delete_me' + create_repo(repo_name, self.REPO_TYPE) + try: + with mock.patch.object(RepoModel, 'delete', crash): + id_, params = _build_data(self.apikey, 'delete_repo', + repoid=repo_name,) + response = api_call(self, params) + + expected = 'failed to delete repository `%s`' % repo_name + self._compare_error(id_, expected, given=response.body) + finally: + destroy_repo(repo_name) + + def test_api_fork_repo(self): + fork_name = 'api-repo-fork' + id_, params = _build_data(self.apikey, 'fork_repo', + repoid=self.REPO, + fork_name=fork_name, + owner=TEST_USER_ADMIN_LOGIN, + ) + response = api_call(self, params) + + ret = { + 'msg': 'Created fork of `%s` as `%s`' % (self.REPO, + fork_name), + 'success': True + } + expected = ret + self._compare_ok(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' + id_, params = _build_data(self.apikey, 'fork_repo', + repoid=self.REPO, + fork_name=fork_name, + owner=owner, + ) + response = api_call(self, params) + expected = 'user `%s` does not exist' % owner + self._compare_error(id_, expected, given=response.body) + + def test_api_fork_repo_fork_exists(self): + fork_name = 'api-repo-fork' + create_fork(fork_name, self.REPO_TYPE, self.REPO) + + try: + fork_name = 'api-repo-fork' + + id_, params = _build_data(self.apikey, 'fork_repo', + repoid=self.REPO, + fork_name=fork_name, + owner=TEST_USER_ADMIN_LOGIN, + ) + response = api_call(self, params) + + expected = "fork `%s` already exist" % fork_name + self._compare_error(id_, expected, given=response.body) + finally: + destroy_repo(fork_name) + + def test_api_fork_repo_repo_exists(self): + fork_name = self.REPO + + id_, params = _build_data(self.apikey, 'fork_repo', + repoid=self.REPO, + fork_name=fork_name, + owner=TEST_USER_ADMIN_LOGIN, + ) + response = api_call(self, params) + + expected = "repo `%s` already exist" % fork_name + self._compare_error(id_, expected, given=response.body) + + @mock.patch.object(RepoModel, 'create_fork', crash) + def test_api_fork_repo_exception_occurred(self): + fork_name = 'api-repo-fork' + id_, params = _build_data(self.apikey, 'fork_repo', + repoid=self.REPO, + fork_name=fork_name, + owner=TEST_USER_ADMIN_LOGIN, + ) + response = api_call(self, params) + + expected = 'failed to fork repository `%s` as `%s`' % (self.REPO, + fork_name) + self._compare_error(id_, expected, given=response.body) + + def test_api_get_users_group(self): + id_, params = _build_data(self.apikey, 'get_users_group', + usersgroupid=TEST_USERS_GROUP) + response = api_call(self, params) + + users_group = UsersGroupModel().get_group(TEST_USERS_GROUP) + members = [] + for user in users_group.members: + user = user.user + members.append(user.get_api_data()) + + ret = users_group.get_api_data() + ret['members'] = members + expected = ret + self._compare_ok(id_, expected, given=response.body) + + def test_api_get_users_groups(self): + + make_users_group('test_users_group2') + + id_, params = _build_data(self.apikey, 'get_users_groups',) + response = api_call(self, params) + + expected = [] + for gr_name in [TEST_USERS_GROUP, 'test_users_group2']: + users_group = UsersGroupModel().get_group(gr_name) + ret = users_group.get_api_data() + expected.append(ret) + self._compare_ok(id_, expected, given=response.body) + + UsersGroupModel().delete(users_group='test_users_group2') + Session().commit() + + def test_api_create_users_group(self): + group_name = 'some_new_group' + id_, params = _build_data(self.apikey, 'create_users_group', + group_name=group_name) + response = api_call(self, params) + + ret = { + 'msg': 'created new users group `%s`' % group_name, + 'users_group': jsonify(UsersGroupModel()\ + .get_by_name(group_name)\ + .get_api_data()) + } + expected = ret + self._compare_ok(id_, expected, given=response.body) + + destroy_users_group(group_name) + + def test_api_get_users_group_that_exist(self): + id_, params = _build_data(self.apikey, 'create_users_group', + group_name=TEST_USERS_GROUP) + response = api_call(self, params) + + expected = "users group `%s` already exist" % TEST_USERS_GROUP + self._compare_error(id_, expected, given=response.body) + + @mock.patch.object(UsersGroupModel, 'create', crash) + def test_api_get_users_group_exception_occurred(self): + group_name = 'exception_happens' + id_, params = _build_data(self.apikey, 'create_users_group', + group_name=group_name) + response = api_call(self, params) + + expected = 'failed to create group `%s`' % group_name + self._compare_error(id_, expected, given=response.body) + + def test_api_add_user_to_users_group(self): + gr_name = 'test_group' + UsersGroupModel().create(gr_name) + Session().commit() + id_, params = _build_data(self.apikey, 'add_user_to_users_group', + usersgroupid=gr_name, + userid=TEST_USER_ADMIN_LOGIN) + response = api_call(self, params) + + expected = { + 'msg': 'added member `%s` to users group `%s`' % ( + TEST_USER_ADMIN_LOGIN, gr_name + ), + 'success': True} + self._compare_ok(id_, expected, given=response.body) + + UsersGroupModel().delete(users_group=gr_name) + Session().commit() + + def test_api_add_user_to_users_group_that_doesnt_exist(self): + id_, params = _build_data(self.apikey, 'add_user_to_users_group', + usersgroupid='false-group', + userid=TEST_USER_ADMIN_LOGIN) + response = api_call(self, params) + + expected = 'users group `%s` does not exist' % 'false-group' + self._compare_error(id_, expected, given=response.body) + + @mock.patch.object(UsersGroupModel, 'add_user_to_group', crash) + def test_api_add_user_to_users_group_exception_occurred(self): + gr_name = 'test_group' + UsersGroupModel().create(gr_name) + Session().commit() + id_, params = _build_data(self.apikey, 'add_user_to_users_group', + usersgroupid=gr_name, + userid=TEST_USER_ADMIN_LOGIN) + response = api_call(self, params) + + expected = 'failed to add member to users group `%s`' % gr_name + self._compare_error(id_, expected, given=response.body) + + UsersGroupModel().delete(users_group=gr_name) + Session().commit() + + def test_api_remove_user_from_users_group(self): + gr_name = 'test_group_3' + gr = UsersGroupModel().create(gr_name) + UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN) + Session().commit() + id_, params = _build_data(self.apikey, 'remove_user_from_users_group', + usersgroupid=gr_name, + userid=TEST_USER_ADMIN_LOGIN) + response = api_call(self, params) + + expected = { + 'msg': 'removed member `%s` from users group `%s`' % ( + TEST_USER_ADMIN_LOGIN, gr_name + ), + 'success': True} + self._compare_ok(id_, expected, given=response.body) + + UsersGroupModel().delete(users_group=gr_name) + Session().commit() + + @mock.patch.object(UsersGroupModel, 'remove_user_from_group', crash) + def test_api_remove_user_from_users_group_exception_occurred(self): + gr_name = 'test_group_3' + gr = UsersGroupModel().create(gr_name) + UsersGroupModel().add_user_to_group(gr, user=TEST_USER_ADMIN_LOGIN) + Session().commit() + id_, params = _build_data(self.apikey, 'remove_user_from_users_group', + usersgroupid=gr_name, + userid=TEST_USER_ADMIN_LOGIN) + response = api_call(self, params) + + expected = 'failed to remove member from users group `%s`' % gr_name + self._compare_error(id_, expected, given=response.body) + + UsersGroupModel().delete(users_group=gr_name) + Session().commit() + + @parameterized.expand([('none', 'repository.none'), + ('read', 'repository.read'), + ('write', 'repository.write'), + ('admin', 'repository.admin')]) + def test_api_grant_user_permission(self, name, perm): + id_, params = _build_data(self.apikey, 'grant_user_permission', + repoid=self.REPO, + userid=TEST_USER_ADMIN_LOGIN, + perm=perm) + response = api_call(self, params) + + ret = { + 'msg': 'Granted perm: `%s` for user: `%s` in repo: `%s`' % ( + perm, TEST_USER_ADMIN_LOGIN, self.REPO + ), + 'success': True + } + expected = ret + self._compare_ok(id_, expected, given=response.body) + + def test_api_grant_user_permission_wrong_permission(self): + perm = 'haha.no.permission' + id_, params = _build_data(self.apikey, 'grant_user_permission', + repoid=self.REPO, + userid=TEST_USER_ADMIN_LOGIN, + perm=perm) + response = api_call(self, params) + + expected = 'permission `%s` does not exist' % perm + self._compare_error(id_, expected, given=response.body) + + @mock.patch.object(RepoModel, 'grant_user_permission', crash) + def test_api_grant_user_permission_exception_when_adding(self): + perm = 'repository.read' + id_, params = _build_data(self.apikey, 'grant_user_permission', + repoid=self.REPO, + userid=TEST_USER_ADMIN_LOGIN, + perm=perm) + response = api_call(self, params) + + expected = 'failed to edit permission for user: `%s` in repo: `%s`' % ( + TEST_USER_ADMIN_LOGIN, self.REPO + ) + self._compare_error(id_, expected, given=response.body) + + def test_api_revoke_user_permission(self): + id_, params = _build_data(self.apikey, 'revoke_user_permission', + repoid=self.REPO, + userid=TEST_USER_ADMIN_LOGIN,) + response = api_call(self, params) + + expected = { + 'msg': 'Revoked perm for user: `%s` in repo: `%s`' % ( + TEST_USER_ADMIN_LOGIN, self.REPO + ), + 'success': True + } + self._compare_ok(id_, expected, given=response.body) + + @mock.patch.object(RepoModel, 'revoke_user_permission', crash) + def test_api_revoke_user_permission_exception_when_adding(self): + id_, params = _build_data(self.apikey, 'revoke_user_permission', + repoid=self.REPO, + userid=TEST_USER_ADMIN_LOGIN,) + response = api_call(self, params) + + expected = 'failed to edit permission for user: `%s` in repo: `%s`' % ( + TEST_USER_ADMIN_LOGIN, self.REPO + ) + self._compare_error(id_, expected, given=response.body) + + @parameterized.expand([('none', 'repository.none'), + ('read', 'repository.read'), + ('write', 'repository.write'), + ('admin', 'repository.admin')]) + def test_api_grant_users_group_permission(self, name, perm): + id_, params = _build_data(self.apikey, 'grant_users_group_permission', + repoid=self.REPO, + usersgroupid=TEST_USERS_GROUP, + perm=perm) + response = api_call(self, params) + + ret = { + 'msg': 'Granted perm: `%s` for users group: `%s` in repo: `%s`' % ( + perm, TEST_USERS_GROUP, self.REPO + ), + 'success': True + } + expected = ret + self._compare_ok(id_, expected, given=response.body) + + def test_api_grant_users_group_permission_wrong_permission(self): + perm = 'haha.no.permission' + id_, params = _build_data(self.apikey, 'grant_users_group_permission', + repoid=self.REPO, + usersgroupid=TEST_USERS_GROUP, + perm=perm) + response = api_call(self, params) + + expected = 'permission `%s` does not exist' % perm + self._compare_error(id_, expected, given=response.body) + + @mock.patch.object(RepoModel, 'grant_users_group_permission', crash) + def test_api_grant_users_group_permission_exception_when_adding(self): + perm = 'repository.read' + id_, params = _build_data(self.apikey, 'grant_users_group_permission', + repoid=self.REPO, + usersgroupid=TEST_USERS_GROUP, + perm=perm) + response = api_call(self, params) + + expected = 'failed to edit permission for users group: `%s` in repo: `%s`' % ( + TEST_USERS_GROUP, self.REPO + ) + self._compare_error(id_, expected, given=response.body) + + def test_api_revoke_users_group_permission(self): + RepoModel().grant_users_group_permission(repo=self.REPO, + group_name=TEST_USERS_GROUP, + perm='repository.read') + Session().commit() + id_, params = _build_data(self.apikey, 'revoke_users_group_permission', + repoid=self.REPO, + usersgroupid=TEST_USERS_GROUP,) + response = api_call(self, params) + + expected = { + 'msg': 'Revoked perm for users group: `%s` in repo: `%s`' % ( + TEST_USERS_GROUP, self.REPO + ), + 'success': True + } + self._compare_ok(id_, expected, given=response.body) + + @mock.patch.object(RepoModel, 'revoke_users_group_permission', crash) + def test_api_revoke_users_group_permission_exception_when_adding(self): + + id_, params = _build_data(self.apikey, 'revoke_users_group_permission', + repoid=self.REPO, + usersgroupid=TEST_USERS_GROUP,) + response = api_call(self, params) + + expected = 'failed to edit permission for users group: `%s` in repo: `%s`' % ( + TEST_USERS_GROUP, self.REPO + ) + self._compare_error(id_, expected, given=response.body) diff --git a/rhodecode/tests/api/test_api_git.py b/rhodecode/tests/api/test_api_git.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/api/test_api_git.py @@ -0,0 +1,7 @@ +from rhodecode.tests import * +from rhodecode.tests.api.api_base import BaseTestApi + + +class TestGitApi(BaseTestApi, TestController): + REPO = GIT_REPO + REPO_TYPE = 'git' diff --git a/rhodecode/tests/api/test_api_hg.py b/rhodecode/tests/api/test_api_hg.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/api/test_api_hg.py @@ -0,0 +1,7 @@ +from rhodecode.tests import * +from rhodecode.tests.api.api_base import BaseTestApi + + +class TestHgApi(BaseTestApi, TestController): + REPO = HG_REPO + REPO_TYPE = 'hg' diff --git a/rhodecode/tests/functional/test_admin_notifications.py b/rhodecode/tests/functional/test_admin_notifications.py --- a/rhodecode/tests/functional/test_admin_notifications.py +++ b/rhodecode/tests/functional/test_admin_notifications.py @@ -1,25 +1,25 @@ from rhodecode.tests import * -from rhodecode.model.db import Notification, User, UserNotification +from rhodecode.model.db import Notification, User from rhodecode.model.user import UserModel from rhodecode.model.notification import NotificationModel -from rhodecode.model.meta import Session + class TestNotificationsController(TestController): - def tearDown(self): for n in Notification.query().all(): inst = Notification.get(n.notification_id) - Session.delete(inst) - Session.commit() + self.Session().delete(inst) + self.Session().commit() def test_index(self): self.log_user() u1 = UserModel().create_or_update(username='u1', password='qweqwe', email='u1@rhodecode.org', - name='u1', lastname='u1').user_id + firstname='u1', lastname='u1') + u1 = u1.user_id response = self.app.get(url('notifications')) self.assertTrue('''
    No notifications here yet
    ''' @@ -30,7 +30,7 @@ class TestNotificationsController(TestCo NotificationModel().create(created_by=u1, subject=u'test_notification_1', body=u'notification_1', recipients=[cur_user]) - Session.commit() + self.Session().commit() response = self.app.get(url('notifications')) self.assertTrue(u'test_notification_1' in response.body) @@ -58,28 +58,27 @@ class TestNotificationsController(TestCo u1 = UserModel().create_or_update(username='u1', password='qweqwe', email='u1@rhodecode.org', - name='u1', lastname='u1') + firstname='u1', lastname='u1') u2 = UserModel().create_or_update(username='u2', password='qweqwe', email='u2@rhodecode.org', - name='u2', lastname='u2') + firstname='u2', lastname='u2') # make notifications notification = NotificationModel().create(created_by=cur_user, subject=u'test', body=u'hi there', recipients=[cur_user, u1, u2]) - Session.commit() + self.Session().commit() u1 = User.get(u1.user_id) u2 = User.get(u2.user_id) # check DB - get_notif = lambda un:[x.notification for x in un] + get_notif = lambda un: [x.notification for x in un] self.assertEqual(get_notif(cur_user.notifications), [notification]) self.assertEqual(get_notif(u1.notifications), [notification]) self.assertEqual(get_notif(u2.notifications), [notification]) cur_usr_id = cur_user.user_id - response = self.app.delete(url('notification', notification_id= notification.notification_id)) @@ -87,19 +86,15 @@ class TestNotificationsController(TestCo cur_user = User.get(cur_usr_id) self.assertEqual(cur_user.notifications, []) - -# def test_delete_browser_fakeout(self): -# response = self.app.post(url('notification', notification_id=1), params=dict(_method='delete')) - def test_show(self): self.log_user() cur_user = self._get_logged_user() u1 = UserModel().create_or_update(username='u1', password='qweqwe', email='u1@rhodecode.org', - name='u1', lastname='u1') + firstname='u1', lastname='u1') u2 = UserModel().create_or_update(username='u2', password='qweqwe', email='u2@rhodecode.org', - name='u2', lastname='u2') + firstname='u2', lastname='u2') notification = NotificationModel().create(created_by=cur_user, subject=u'test', @@ -108,12 +103,3 @@ class TestNotificationsController(TestCo response = self.app.get(url('notification', notification_id=notification.notification_id)) - -# def test_show_as_xml(self): -# response = self.app.get(url('formatted_notification', notification_id=1, format='xml')) -# -# def test_edit(self): -# response = self.app.get(url('edit_notification', notification_id=1)) -# -# def test_edit_as_xml(self): -# response = self.app.get(url('formatted_edit_notification', notification_id=1, format='xml')) diff --git a/rhodecode/tests/functional/test_admin_repos.py b/rhodecode/tests/functional/test_admin_repos.py --- a/rhodecode/tests/functional/test_admin_repos.py +++ b/rhodecode/tests/functional/test_admin_repos.py @@ -3,8 +3,11 @@ import os from rhodecode.lib import vcs -from rhodecode.model.db import Repository +from rhodecode.model.db import Repository, RepoGroup from rhodecode.tests import * +from rhodecode.model.repos_group import ReposGroupModel +from rhodecode.model.repo import RepoModel + class TestAdminReposController(TestController): @@ -24,17 +27,19 @@ class TestAdminReposController(TestContr repo_name = NEW_HG_REPO description = 'description for newly created repo' private = False - response = self.app.post(url('repos'), {'repo_name':repo_name, - 'repo_type':'hg', - 'clone_uri':'', - 'repo_group':'', - 'description':description, - 'private':private}) - self.checkSessionFlash(response, 'created repository %s' % (repo_name)) + response = self.app.post(url('repos'), {'repo_name': repo_name, + 'repo_type': 'hg', + 'clone_uri': '', + 'repo_group': '', + 'description': description, + 'private': private, + 'landing_rev': 'tip'}) + self.checkSessionFlash(response, + 'created repository %s' % (repo_name)) #test if the repo was created in the database - new_repo = self.Session.query(Repository).filter(Repository.repo_name == - repo_name).one() + new_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name).one() self.assertEqual(new_repo.repo_name, repo_name) self.assertEqual(new_repo.description, description) @@ -42,15 +47,13 @@ class TestAdminReposController(TestContr #test if repository is visible in the list ? response = response.follow() - self.assertTrue(repo_name in response.body) - + response.mustcontain(repo_name) #test if repository was created on filesystem try: vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name)) except: - self.fail('no repo in filesystem') - + self.fail('no repo %s in filesystem' % repo_name) def test_create_hg_non_ascii(self): self.log_user() @@ -60,18 +63,19 @@ class TestAdminReposController(TestContr description = 'description for newly created repo' + non_ascii description_unicode = description.decode('utf8') private = False - response = self.app.post(url('repos'), {'repo_name':repo_name, - 'repo_type':'hg', - 'clone_uri':'', - 'repo_group':'', - 'description':description, - 'private':private}) + response = self.app.post(url('repos'), {'repo_name': repo_name, + 'repo_type': 'hg', + 'clone_uri': '', + 'repo_group': '', + 'description': description, + 'private': private, + 'landing_rev': 'tip'}) self.checkSessionFlash(response, 'created repository %s' % (repo_name_unicode)) #test if the repo was created in the database - new_repo = self.Session.query(Repository).filter(Repository.repo_name == - repo_name_unicode).one() + new_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name_unicode).one() self.assertEqual(new_repo.repo_name, repo_name_unicode) self.assertEqual(new_repo.description, description_unicode) @@ -79,52 +83,129 @@ class TestAdminReposController(TestContr #test if repository is visible in the list ? response = response.follow() - self.assertTrue(repo_name in response.body) + response.mustcontain(repo_name) #test if repository was created on filesystem try: vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name)) except: - self.fail('no repo in filesystem') - + self.fail('no repo %s in filesystem' % repo_name) def test_create_hg_in_group(self): - #TODO: write test ! - pass + self.log_user() + + ## create GROUP + group_name = 'sometest' + gr = ReposGroupModel().create(group_name=group_name, + group_description='test',) + self.Session().commit() + + repo_name = 'ingroup' + repo_name_full = RepoGroup.url_sep().join([group_name, repo_name]) + description = 'description for newly created repo' + private = False + response = self.app.post(url('repos'), {'repo_name': repo_name, + 'repo_type': 'hg', + 'clone_uri': '', + 'repo_group': gr.group_id, + 'description': description, + 'private': private, + 'landing_rev': 'tip'}) + self.checkSessionFlash(response, + 'created repository %s' % (repo_name)) + + #test if the repo was created in the database + new_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name_full).one() + + self.assertEqual(new_repo.repo_name, repo_name_full) + self.assertEqual(new_repo.description, description) + + #test if repository is visible in the list ? + response = response.follow() + + response.mustcontain(repo_name_full) + + #test if repository was created on filesystem + try: + vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name_full)) + except: + ReposGroupModel().delete(group_name) + self.Session().commit() + self.fail('no repo %s in filesystem' % repo_name) + + RepoModel().delete(repo_name_full) + ReposGroupModel().delete(group_name) + self.Session().commit() def test_create_git(self): - return self.log_user() repo_name = NEW_GIT_REPO description = 'description for newly created repo' private = False - response = self.app.post(url('repos'), {'repo_name':repo_name, - 'repo_type':'git', - 'clone_uri':'', - 'repo_group':'', - 'description':description, - 'private':private}) - + response = self.app.post(url('repos'), {'repo_name': repo_name, + 'repo_type': 'git', + 'clone_uri': '', + 'repo_group': '', + 'description': description, + 'private': private, + 'landing_rev': 'tip'}) + self.checkSessionFlash(response, + 'created repository %s' % (repo_name)) - #test if we have a message for that repository - assert '''created repository %s''' % (repo_name) in response.session['flash'][0], 'No flash message about new repo' + #test if the repo was created in the database + new_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name).one() - #test if the fork was created in the database - new_repo = self.Session.query(Repository).filter(Repository.repo_name == repo_name).one() - - assert new_repo.repo_name == repo_name, 'wrong name of repo name in db' - assert new_repo.description == description, 'wrong description' + self.assertEqual(new_repo.repo_name, repo_name) + self.assertEqual(new_repo.description, description) #test if repository is visible in the list ? response = response.follow() - assert repo_name in response.body, 'missing new repo from the main repos list' + response.mustcontain(repo_name) #test if repository was created on filesystem try: vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name)) except: - assert False , 'no repo in filesystem' + self.fail('no repo %s in filesystem' % repo_name) + + def test_create_git_non_ascii(self): + self.log_user() + non_ascii = "ąęł" + repo_name = "%s%s" % (NEW_GIT_REPO, non_ascii) + repo_name_unicode = repo_name.decode('utf8') + description = 'description for newly created repo' + non_ascii + description_unicode = description.decode('utf8') + private = False + response = self.app.post(url('repos'), {'repo_name': repo_name, + 'repo_type': 'git', + 'clone_uri': '', + 'repo_group': '', + 'description': description, + 'private': private, + 'landing_rev': 'tip'}) + self.checkSessionFlash(response, + 'created repository %s' % (repo_name_unicode)) + + #test if the repo was created in the database + new_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name_unicode).one() + + self.assertEqual(new_repo.repo_name, repo_name_unicode) + self.assertEqual(new_repo.description, description_unicode) + + #test if repository is visible in the list ? + response = response.follow() + + response.mustcontain(repo_name) + + #test if repository was created on filesystem + try: + vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name)) + except: + self.fail('no repo %s in filesystem' % repo_name) def test_new(self): self.log_user() @@ -140,27 +221,24 @@ class TestAdminReposController(TestContr response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='put')) - def test_delete(self): + def test_delete_hg(self): self.log_user() repo_name = 'vcs_test_new_to_delete' description = 'description for newly created repo' private = False - - response = self.app.post(url('repos'), {'repo_name':repo_name, - 'repo_type':'hg', - 'clone_uri':'', - 'repo_group':'', - 'description':description, - 'private':private}) - self.assertTrue('flash' in response.session) - - #test if we have a message for that repository - self.assertTrue('''created repository %s''' % (repo_name) in - response.session['flash'][0]) + response = self.app.post(url('repos'), {'repo_name': repo_name, + 'repo_type': 'hg', + 'clone_uri': '', + 'repo_group': '', + 'description': description, + 'private': private, + 'landing_rev': 'tip'}) + self.checkSessionFlash(response, + 'created repository %s' % (repo_name)) #test if the repo was created in the database - new_repo = self.Session.query(Repository).filter(Repository.repo_name == - repo_name).one() + new_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name).one() self.assertEqual(new_repo.repo_name, repo_name) self.assertEqual(new_repo.description, description) @@ -168,8 +246,13 @@ class TestAdminReposController(TestContr #test if repository is visible in the list ? response = response.follow() - self.assertTrue(repo_name in response.body) + response.mustcontain(repo_name) + #test if repository was created on filesystem + try: + vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name)) + except: + self.fail('no repo %s in filesystem' % repo_name) response = self.app.delete(url('repo', repo_name=repo_name)) @@ -179,32 +262,79 @@ class TestAdminReposController(TestContr response.follow() #check if repo was deleted from db - deleted_repo = self.Session.query(Repository).filter(Repository.repo_name - == repo_name).scalar() + deleted_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name).scalar() self.assertEqual(deleted_repo, None) + self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)), + False) + + def test_delete_git(self): + self.log_user() + repo_name = 'vcs_test_new_to_delete' + description = 'description for newly created repo' + private = False + response = self.app.post(url('repos'), {'repo_name': repo_name, + 'repo_type': 'git', + 'clone_uri': '', + 'repo_group': '', + 'description': description, + 'private': private, + 'landing_rev': 'tip'}) + self.checkSessionFlash(response, + 'created repository %s' % (repo_name)) + + #test if the repo was created in the database + new_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name).one() + + self.assertEqual(new_repo.repo_name, repo_name) + self.assertEqual(new_repo.description, description) + + #test if repository is visible in the list ? + response = response.follow() + + response.mustcontain(repo_name) + + #test if repository was created on filesystem + try: + vcs.get_repo(os.path.join(TESTS_TMP_PATH, repo_name)) + except: + self.fail('no repo %s in filesystem' % repo_name) + + response = self.app.delete(url('repo', repo_name=repo_name)) + + self.assertTrue('''deleted repository %s''' % (repo_name) in + response.session['flash'][0]) + + response.follow() + + #check if repo was deleted from db + deleted_repo = self.Session().query(Repository)\ + .filter(Repository.repo_name == repo_name).scalar() + + self.assertEqual(deleted_repo, None) + + self.assertEqual(os.path.isdir(os.path.join(TESTS_TMP_PATH, repo_name)), + False) def test_delete_repo_with_group(self): #TODO: pass - def test_delete_browser_fakeout(self): response = self.app.post(url('repo', repo_name=HG_REPO), params=dict(_method='delete')) - def test_show(self): + def test_show_hg(self): self.log_user() response = self.app.get(url('repo', repo_name=HG_REPO)) - def test_show_as_xml(self): - response = self.app.get(url('formatted_repo', repo_name=HG_REPO, - format='xml')) + def test_show_git(self): + self.log_user() + response = self.app.get(url('repo', repo_name=GIT_REPO)) + def test_edit(self): response = self.app.get(url('edit_repo', repo_name=HG_REPO)) - - def test_edit_as_xml(self): - response = self.app.get(url('formatted_edit_repo', repo_name=HG_REPO, - format='xml')) diff --git a/rhodecode/tests/functional/test_admin_settings.py b/rhodecode/tests/functional/test_admin_settings.py --- a/rhodecode/tests/functional/test_admin_settings.py +++ b/rhodecode/tests/functional/test_admin_settings.py @@ -1,8 +1,12 @@ # -*- coding: utf-8 -*- from rhodecode.lib.auth import get_crypt_password, check_password -from rhodecode.model.db import User, RhodeCodeSetting +from rhodecode.model.db import User, RhodeCodeSetting, Repository from rhodecode.tests import * +from rhodecode.lib import helpers as h +from rhodecode.model.user import UserModel +from rhodecode.model.scm import ScmModel + class TestAdminSettingsController(TestController): @@ -47,7 +51,6 @@ class TestAdminSettingsController(TestCo response = self.app.get(url('formatted_admin_edit_setting', setting_id=1, format='xml')) - def test_ga_code_active(self): self.log_user() old_title = 'RhodeCode' @@ -67,8 +70,7 @@ class TestAdminSettingsController(TestCo .get_app_settings()['rhodecode_ga_code'], new_ga_code) response = response.follow() - self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code - in response.body) + response.mustcontain("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code) def test_ga_code_inactive(self): self.log_user() @@ -89,9 +91,8 @@ class TestAdminSettingsController(TestCo .get_app_settings()['rhodecode_ga_code'], new_ga_code) response = response.follow() - self.assertTrue("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code - not in response.body) - + self.assertFalse("""_gaq.push(['_setAccount', '%s']);""" % new_ga_code + in response.body) def test_title_change(self): self.log_user() @@ -114,9 +115,7 @@ class TestAdminSettingsController(TestCo new_title.decode('utf-8')) response = response.follow() - self.assertTrue("""

    %s

    """ % new_title - in response.body) - + response.mustcontain("""

    %s

    """ % new_title) def test_my_account(self): self.log_user() @@ -124,89 +123,144 @@ class TestAdminSettingsController(TestCo self.assertTrue('value="test_admin' in response.body) - def test_my_account_update(self): - self.log_user() - - new_email = 'new@mail.pl' - new_name = 'NewName' - new_lastname = 'NewLastname' - new_password = 'test123' - + @parameterized.expand([('firstname', 'new_username'), + ('lastname', 'new_username'), + ('admin', True), + ('admin', False), + ('ldap_dn', 'test'), + ('ldap_dn', None), + ('active', False), + ('active', True), + ('email', 'some@email.com'), + ]) + def test_my_account_update(self, name, expected): + uname = 'testme' + usr = UserModel().create_or_update(username=uname, password='qweqwe', + email='testme@rhodecod.org') + self.Session().commit() + params = usr.get_api_data() + user_id = usr.user_id + self.log_user(username=uname, password='qweqwe') + params.update({name: expected}) + params.update({'password_confirmation': ''}) + params.update({'new_password': ''}) - response = self.app.post(url('admin_settings_my_account_update'), - params=dict(_method='put', - username='test_admin', - new_password=new_password, - password_confirmation = new_password, - password='', - name=new_name, - lastname=new_lastname, - email=new_email,)) - response.follow() - - assert 'Your account was updated successfully' in response.session['flash'][0][1], 'no flash message about success of change' - user = self.Session.query(User).filter(User.username == 'test_admin').one() - assert user.email == new_email , 'incorrect user email after update got %s vs %s' % (user.email, new_email) - assert user.name == new_name, 'updated field mismatch %s vs %s' % (user.name, new_name) - assert user.lastname == new_lastname, 'updated field mismatch %s vs %s' % (user.lastname, new_lastname) - assert check_password(new_password, user.password) is True, 'password field mismatch %s vs %s' % (user.password, new_password) + try: + response = self.app.put(url('admin_settings_my_account_update', + id=user_id), params) - #bring back the admin settings - old_email = 'test_admin@mail.com' - old_name = 'RhodeCode' - old_lastname = 'Admin' - old_password = 'test12' + self.checkSessionFlash(response, + 'Your account was updated successfully') + + updated_user = User.get_by_username(uname) + updated_params = updated_user.get_api_data() + updated_params.update({'password_confirmation': ''}) + updated_params.update({'new_password': ''}) - response = self.app.post(url('admin_settings_my_account_update'), params=dict( - _method='put', - username='test_admin', - new_password=old_password, - password_confirmation = old_password, - password='', - name=old_name, - lastname=old_lastname, - email=old_email,)) + params['last_login'] = updated_params['last_login'] + if name == 'email': + params['emails'] = [expected] + if name == 'ldap_dn': + #cannot update this via form + params['ldap_dn'] = None + if name == 'active': + #my account cannot deactivate account + params['active'] = True + if name == 'admin': + #my account cannot make you an admin ! + params['admin'] = False - response.follow() - self.checkSessionFlash(response, - 'Your account was updated successfully') + self.assertEqual(params, updated_params) - user = self.Session.query(User).filter(User.username == 'test_admin').one() - assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email) - - assert user.email == old_email , 'incorrect user email after update got %s vs %s' % (user.email, old_email) - assert user.name == old_name, 'updated field mismatch %s vs %s' % (user.name, old_name) - assert user.lastname == old_lastname, 'updated field mismatch %s vs %s' % (user.lastname, old_lastname) - assert check_password(old_password, user.password) is True , 'password updated field mismatch %s vs %s' % (user.password, old_password) - + finally: + UserModel().delete('testme') def test_my_account_update_err_email_exists(self): self.log_user() - new_email = 'test_regular@mail.com'#already exisitn email - response = self.app.post(url('admin_settings_my_account_update'), params=dict( - _method='put', - username='test_admin', - new_password='test12', - password_confirmation = 'test122', - name='NewName', - lastname='NewLastname', - email=new_email,)) + new_email = 'test_regular@mail.com' # already exisitn email + response = self.app.put(url('admin_settings_my_account_update'), + params=dict( + username='test_admin', + new_password='test12', + password_confirmation='test122', + firstname='NewName', + lastname='NewLastname', + email=new_email,) + ) - assert 'This e-mail address is already taken' in response.body, 'Missing error message about existing email' - + response.mustcontain('This e-mail address is already taken') def test_my_account_update_err(self): self.log_user('test_regular2', 'test12') new_email = 'newmail.pl' - response = self.app.post(url('admin_settings_my_account_update'), params=dict( - _method='put', - username='test_admin', - new_password='test12', - password_confirmation = 'test122', - name='NewName', - lastname='NewLastname', - email=new_email,)) - assert 'An email address must contain a single @' in response.body, 'Missing error message about wrong email' - assert 'This username already exists' in response.body, 'Missing error message about existing user' + response = self.app.post(url('admin_settings_my_account_update'), + params=dict( + _method='put', + username='test_admin', + new_password='test12', + password_confirmation='test122', + firstname='NewName', + lastname='NewLastname', + email=new_email,) + ) + + response.mustcontain('An email address must contain a single @') + from rhodecode.model import validators + msg = validators.ValidUsername(edit=False, + old_data={})._messages['username_exists'] + msg = h.html_escape(msg % {'username': 'test_admin'}) + response.mustcontain(u"%s" % msg) + + def test_set_repo_fork_has_no_self_id(self): + self.log_user() + repo = Repository.get_by_repo_name(HG_REPO) + response = self.app.get(url('edit_repo', repo_name=HG_REPO)) + opt = """""" % repo.repo_id + assert opt not in response.body + + def test_set_fork_of_repo(self): + self.log_user() + repo = Repository.get_by_repo_name(HG_REPO) + repo2 = Repository.get_by_repo_name(GIT_REPO) + response = self.app.put(url('repo_as_fork', repo_name=HG_REPO), + params=dict( + id_fork_of=repo2.repo_id + )) + repo = Repository.get_by_repo_name(HG_REPO) + repo2 = Repository.get_by_repo_name(GIT_REPO) + self.checkSessionFlash(response, + 'Marked repo %s as fork of %s' % (repo.repo_name, repo2.repo_name)) + + assert repo.fork == repo2 + response = response.follow() + # check if given repo is selected + + opt = """""" % ( + repo2.repo_id, repo2.repo_name) + response.mustcontain(opt) + + # clean session flash + #response = self.app.get(url('edit_repo', repo_name=HG_REPO)) + + ## mark it as None + response = self.app.put(url('repo_as_fork', repo_name=HG_REPO), + params=dict( + id_fork_of=None + )) + repo = Repository.get_by_repo_name(HG_REPO) + repo2 = Repository.get_by_repo_name(GIT_REPO) + self.checkSessionFlash(response, + 'Marked repo %s as fork of %s' % (repo.repo_name, "Nothing")) + assert repo.fork == None + + def test_set_fork_of_same_repo(self): + self.log_user() + repo = Repository.get_by_repo_name(HG_REPO) + response = self.app.put(url('repo_as_fork', repo_name=HG_REPO), + params=dict( + id_fork_of=repo.repo_id + )) + self.checkSessionFlash(response, + 'An error occurred during this operation') diff --git a/rhodecode/tests/functional/test_admin_users.py b/rhodecode/tests/functional/test_admin_users.py --- a/rhodecode/tests/functional/test_admin_users.py +++ b/rhodecode/tests/functional/test_admin_users.py @@ -1,8 +1,13 @@ +from sqlalchemy.orm.exc import NoResultFound + from rhodecode.tests import * from rhodecode.model.db import User, Permission from rhodecode.lib.auth import check_password -from sqlalchemy.orm.exc import NoResultFound from rhodecode.model.user import UserModel +from rhodecode.model import validators +from rhodecode.lib import helpers as h +from rhodecode.model.meta import Session + class TestAdminUsersController(TestController): @@ -24,30 +29,28 @@ class TestAdminUsersController(TestContr email = 'mail@mail.com' response = self.app.post(url('users'), - {'username':username, - 'password':password, - 'password_confirmation':password_confirmation, - 'name':name, - 'active':True, - 'lastname':lastname, - 'email':email}) + {'username': username, + 'password': password, + 'password_confirmation': password_confirmation, + 'firstname': name, + 'active': True, + 'lastname': lastname, + 'email': email}) - - self.assertTrue('''created user %s''' % (username) in - response.session['flash'][0]) + self.checkSessionFlash(response, '''created user %s''' % (username)) new_user = self.Session.query(User).\ filter(User.username == username).one() - self.assertEqual(new_user.username,username) - self.assertEqual(check_password(password, new_user.password),True) - self.assertEqual(new_user.name,name) - self.assertEqual(new_user.lastname,lastname) - self.assertEqual(new_user.email,email) + self.assertEqual(new_user.username, username) + self.assertEqual(check_password(password, new_user.password), True) + self.assertEqual(new_user.name, name) + self.assertEqual(new_user.lastname, lastname) + self.assertEqual(new_user.email, email) response.follow() response = response.follow() - self.assertTrue("""edit">newtestuser""" in response.body) + response.mustcontain("""newtestuser""") def test_create_err(self): self.log_user() @@ -57,16 +60,18 @@ class TestAdminUsersController(TestContr lastname = 'lastname' email = 'errmail.com' - response = self.app.post(url('users'), {'username':username, - 'password':password, - 'name':name, - 'active':False, - 'lastname':lastname, - 'email':email}) + response = self.app.post(url('users'), {'username': username, + 'password': password, + 'name': name, + 'active': False, + 'lastname': lastname, + 'email': email}) - self.assertTrue("""Invalid username""" in response.body) - self.assertTrue("""Please enter a value""" in response.body) - self.assertTrue("""An email address must contain a single @""" in response.body) + msg = validators.ValidUsername(False, {})._messages['system_invalid_username'] + msg = h.html_escape(msg % {'username': 'new_user'}) + response.mustcontain("""%s""" % msg) + response.mustcontain("""Please enter a value""") + response.mustcontain("""An email address must contain a single @""") def get_user(): self.Session.query(User).filter(User.username == username).one() @@ -80,8 +85,45 @@ class TestAdminUsersController(TestContr def test_new_as_xml(self): response = self.app.get(url('formatted_new_user', format='xml')) - def test_update(self): - response = self.app.put(url('user', id=1)) + @parameterized.expand([('firstname', 'new_username'), + ('lastname', 'new_username'), + ('admin', True), + ('admin', False), + ('ldap_dn', 'test'), + ('ldap_dn', None), + ('active', False), + ('active', True), + ('email', 'some@email.com'), + ]) + def test_update(self, name, expected): + self.log_user() + uname = 'testme' + usr = UserModel().create_or_update(username=uname, password='qweqwe', + email='testme@rhodecod.org') + self.Session().commit() + params = usr.get_api_data() + params.update({name: expected}) + params.update({'password_confirmation': ''}) + params.update({'new_password': ''}) + if name == 'email': + params['emails'] = [expected] + if name == 'ldap_dn': + #cannot update this via form + params['ldap_dn'] = None + try: + response = self.app.put(url('user', id=usr.user_id), params) + + self.checkSessionFlash(response, '''User updated successfully''') + + updated_user = User.get_by_username(uname) + updated_params = updated_user.get_api_data() + updated_params.update({'password_confirmation': ''}) + updated_params.update({'new_password': ''}) + + self.assertEqual(params, updated_params) + + finally: + UserModel().delete('testme') def test_update_browser_fakeout(self): response = self.app.post(url('user', id=1), params=dict(_method='put')) @@ -94,13 +136,13 @@ class TestAdminUsersController(TestContr lastname = 'lastname' email = 'todeletemail@mail.com' - response = self.app.post(url('users'), {'username':username, - 'password':password, - 'password_confirmation':password, - 'name':name, - 'active':True, - 'lastname':lastname, - 'email':email}) + response = self.app.post(url('users'), {'username': username, + 'password': password, + 'password_confirmation': password, + 'firstname': name, + 'active': True, + 'lastname': lastname, + 'email': email}) response = response.follow() @@ -111,7 +153,6 @@ class TestAdminUsersController(TestContr self.assertTrue("""successfully deleted user""" in response.session['flash'][0]) - def test_delete_browser_fakeout(self): response = self.app.post(url('user', id=1), params=dict(_method='delete')) @@ -127,53 +168,123 @@ class TestAdminUsersController(TestContr user = User.get_by_username(TEST_USER_ADMIN_LOGIN) response = self.app.get(url('edit_user', id=user.user_id)) - def test_add_perm_create_repo(self): self.log_user() perm_none = Permission.get_by_key('hg.create.none') perm_create = Permission.get_by_key('hg.create.repository') - user = User.get_by_username(TEST_USER_REGULAR_LOGIN) - + user = UserModel().create_or_update(username='dummy', password='qwe', + email='dummy', firstname='a', + lastname='b') + Session().commit() + uid = user.user_id - #User should have None permission on creation repository - self.assertEqual(UserModel().has_perm(user, perm_none), False) - self.assertEqual(UserModel().has_perm(user, perm_create), False) + try: + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(user, perm_none), False) + self.assertEqual(UserModel().has_perm(user, perm_create), False) - response = self.app.post(url('user_perm', id=user.user_id), - params=dict(_method='put', - create_repo_perm=True)) + response = self.app.post(url('user_perm', id=uid), + params=dict(_method='put', + create_repo_perm=True)) + + perm_none = Permission.get_by_key('hg.create.none') + perm_create = Permission.get_by_key('hg.create.repository') - perm_none = Permission.get_by_key('hg.create.none') - perm_create = Permission.get_by_key('hg.create.repository') - - user = User.get_by_username(TEST_USER_REGULAR_LOGIN) - #User should have None permission on creation repository - self.assertEqual(UserModel().has_perm(user, perm_none), False) - self.assertEqual(UserModel().has_perm(user, perm_create), True) + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(uid, perm_none), False) + self.assertEqual(UserModel().has_perm(uid, perm_create), True) + finally: + UserModel().delete(uid) + Session().commit() def test_revoke_perm_create_repo(self): self.log_user() perm_none = Permission.get_by_key('hg.create.none') perm_create = Permission.get_by_key('hg.create.repository') - user = User.get_by_username(TEST_USER_REGULAR2_LOGIN) + user = UserModel().create_or_update(username='dummy', password='qwe', + email='dummy', firstname='a', + lastname='b') + Session().commit() + uid = user.user_id + try: + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(user, perm_none), False) + self.assertEqual(UserModel().has_perm(user, perm_create), False) + + response = self.app.post(url('user_perm', id=uid), + params=dict(_method='put')) + + perm_none = Permission.get_by_key('hg.create.none') + perm_create = Permission.get_by_key('hg.create.repository') - #User should have None permission on creation repository - self.assertEqual(UserModel().has_perm(user, perm_none), False) - self.assertEqual(UserModel().has_perm(user, perm_create), False) + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(uid, perm_none), True) + self.assertEqual(UserModel().has_perm(uid, perm_create), False) + finally: + UserModel().delete(uid) + Session().commit() + + def test_add_perm_fork_repo(self): + self.log_user() + perm_none = Permission.get_by_key('hg.fork.none') + perm_fork = Permission.get_by_key('hg.fork.repository') + + user = UserModel().create_or_update(username='dummy', password='qwe', + email='dummy', firstname='a', + lastname='b') + Session().commit() + uid = user.user_id + + try: + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(user, perm_none), False) + self.assertEqual(UserModel().has_perm(user, perm_fork), False) - response = self.app.post(url('user_perm', id=user.user_id), - params=dict(_method='put')) + response = self.app.post(url('user_perm', id=uid), + params=dict(_method='put', + create_repo_perm=True)) + + perm_none = Permission.get_by_key('hg.create.none') + perm_create = Permission.get_by_key('hg.create.repository') + + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(uid, perm_none), False) + self.assertEqual(UserModel().has_perm(uid, perm_create), True) + finally: + UserModel().delete(uid) + Session().commit() + + def test_revoke_perm_fork_repo(self): + self.log_user() + perm_none = Permission.get_by_key('hg.fork.none') + perm_fork = Permission.get_by_key('hg.fork.repository') - perm_none = Permission.get_by_key('hg.create.none') - perm_create = Permission.get_by_key('hg.create.repository') + user = UserModel().create_or_update(username='dummy', password='qwe', + email='dummy', firstname='a', + lastname='b') + Session().commit() + uid = user.user_id + + try: + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(user, perm_none), False) + self.assertEqual(UserModel().has_perm(user, perm_fork), False) - user = User.get_by_username(TEST_USER_REGULAR2_LOGIN) - #User should have None permission on creation repository - self.assertEqual(UserModel().has_perm(user, perm_none), True) - self.assertEqual(UserModel().has_perm(user, perm_create), False) + response = self.app.post(url('user_perm', id=uid), + params=dict(_method='put')) + + perm_none = Permission.get_by_key('hg.create.none') + perm_create = Permission.get_by_key('hg.create.repository') + + #User should have None permission on creation repository + self.assertEqual(UserModel().has_perm(uid, perm_none), True) + self.assertEqual(UserModel().has_perm(uid, perm_create), False) + finally: + UserModel().delete(uid) + Session().commit() def test_edit_as_xml(self): response = self.app.get(url('formatted_edit_user', id=1, format='xml')) diff --git a/rhodecode/tests/functional/test_admin_users_groups.py b/rhodecode/tests/functional/test_admin_users_groups.py --- a/rhodecode/tests/functional/test_admin_users_groups.py +++ b/rhodecode/tests/functional/test_admin_users_groups.py @@ -65,27 +65,48 @@ class TestAdminUsersGroupsController(Tes users_group_name = TEST_USERS_GROUP + 'another2' response = self.app.post(url('users_groups'), {'users_group_name': users_group_name, - 'active':True}) + 'active': True}) response.follow() ug = UsersGroup.get_by_group_name(users_group_name) self.checkSessionFlash(response, 'created users group %s' % users_group_name) - + ## ENABLE REPO CREATE ON A GROUP response = self.app.put(url('users_group_perm', id=ug.users_group_id), {'create_repo_perm': True}) response.follow() ug = UsersGroup.get_by_group_name(users_group_name) p = Permission.get_by_key('hg.create.repository') - # check if user has this perm + p2 = Permission.get_by_key('hg.fork.none') + # check if user has this perms, they should be here since + # defaults are on perms = UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group == ug).all() - perms = [[x.__dict__['users_group_id'], - x.__dict__['permission_id'],] for x in perms] + self.assertEqual( - perms, - [[ug.users_group_id, p.permission_id]] + [[x.users_group_id, x.permission_id, ] for x in perms], + [[ug.users_group_id, p.permission_id], + [ug.users_group_id, p2.permission_id]] + ) + + ## DISABLE REPO CREATE ON A GROUP + response = self.app.put(url('users_group_perm', id=ug.users_group_id), + {}) + + response.follow() + ug = UsersGroup.get_by_group_name(users_group_name) + p = Permission.get_by_key('hg.create.none') + p2 = Permission.get_by_key('hg.fork.none') + # check if user has this perms, they should be here since + # defaults are on + perms = UsersGroupToPerm.query()\ + .filter(UsersGroupToPerm.users_group == ug).all() + + self.assertEqual( + sorted([[x.users_group_id, x.permission_id, ] for x in perms]), + sorted([[ug.users_group_id, p.permission_id], + [ug.users_group_id, p2.permission_id]]) ) # DELETE ! @@ -101,8 +122,77 @@ class TestAdminUsersGroupsController(Tes p = Permission.get_by_key('hg.create.repository') perms = UsersGroupToPerm.query()\ .filter(UsersGroupToPerm.users_group_id == ugid).all() - perms = [[x.__dict__['users_group_id'], - x.__dict__['permission_id'],] for x in perms] + perms = [[x.users_group_id, + x.permission_id, ] for x in perms] + self.assertEqual( + perms, + [] + ) + + def test_enable_repository_fork_on_group(self): + self.log_user() + users_group_name = TEST_USERS_GROUP + 'another2' + response = self.app.post(url('users_groups'), + {'users_group_name': users_group_name, + 'active': True}) + response.follow() + + ug = UsersGroup.get_by_group_name(users_group_name) + self.checkSessionFlash(response, + 'created users group %s' % users_group_name) + ## ENABLE REPO CREATE ON A GROUP + response = self.app.put(url('users_group_perm', id=ug.users_group_id), + {'fork_repo_perm': True}) + + response.follow() + ug = UsersGroup.get_by_group_name(users_group_name) + p = Permission.get_by_key('hg.create.none') + p2 = Permission.get_by_key('hg.fork.repository') + # check if user has this perms, they should be here since + # defaults are on + perms = UsersGroupToPerm.query()\ + .filter(UsersGroupToPerm.users_group == ug).all() + + self.assertEqual( + [[x.users_group_id, x.permission_id, ] for x in perms], + [[ug.users_group_id, p.permission_id], + [ug.users_group_id, p2.permission_id]] + ) + + ## DISABLE REPO CREATE ON A GROUP + response = self.app.put(url('users_group_perm', id=ug.users_group_id), + {}) + + response.follow() + ug = UsersGroup.get_by_group_name(users_group_name) + p = Permission.get_by_key('hg.create.none') + p2 = Permission.get_by_key('hg.fork.none') + # check if user has this perms, they should be here since + # defaults are on + perms = UsersGroupToPerm.query()\ + .filter(UsersGroupToPerm.users_group == ug).all() + + self.assertEqual( + [[x.users_group_id, x.permission_id, ] for x in perms], + [[ug.users_group_id, p.permission_id], + [ug.users_group_id, p2.permission_id]] + ) + + # DELETE ! + ug = UsersGroup.get_by_group_name(users_group_name) + ugid = ug.users_group_id + response = self.app.delete(url('users_group', id=ug.users_group_id)) + response = response.follow() + gr = self.Session.query(UsersGroup)\ + .filter(UsersGroup.users_group_name == + users_group_name).scalar() + + self.assertEqual(gr, None) + p = Permission.get_by_key('hg.fork.repository') + perms = UsersGroupToPerm.query()\ + .filter(UsersGroupToPerm.users_group_id == ugid).all() + perms = [[x.users_group_id, + x.permission_id, ] for x in perms] self.assertEqual( perms, [] diff --git a/rhodecode/tests/functional/test_changelog.py b/rhodecode/tests/functional/test_changelog.py --- a/rhodecode/tests/functional/test_changelog.py +++ b/rhodecode/tests/functional/test_changelog.py @@ -10,8 +10,10 @@ class TestChangelogController(TestContro response.mustcontain("""
    """) response.mustcontain( - """""" + """""" ) response.mustcontain( """154:""" @@ -21,7 +23,7 @@ class TestChangelogController(TestContro response.mustcontain("""Small update at simplevcs app""") response.mustcontain( - """
    3
    """ @@ -29,22 +31,24 @@ class TestChangelogController(TestContro #pagination response = self.app.get(url(controller='changelog', action='index', - repo_name=HG_REPO), {'page':1}) + repo_name=HG_REPO), {'page': 1}) response = self.app.get(url(controller='changelog', action='index', - repo_name=HG_REPO), {'page':2}) + repo_name=HG_REPO), {'page': 2}) response = self.app.get(url(controller='changelog', action='index', - repo_name=HG_REPO), {'page':3}) + repo_name=HG_REPO), {'page': 3}) response = self.app.get(url(controller='changelog', action='index', - repo_name=HG_REPO), {'page':4}) + repo_name=HG_REPO), {'page': 4}) response = self.app.get(url(controller='changelog', action='index', - repo_name=HG_REPO), {'page':5}) + repo_name=HG_REPO), {'page': 5}) response = self.app.get(url(controller='changelog', action='index', - repo_name=HG_REPO), {'page':6}) + repo_name=HG_REPO), {'page': 6}) # Test response after pagination... response.mustcontain( - """""" + """""" ) response.mustcontain( """64:""" @@ -52,7 +56,7 @@ class TestChangelogController(TestContro ) response.mustcontain( - """
    21
    """ diff --git a/rhodecode/tests/functional/test_changeset_comments.py b/rhodecode/tests/functional/test_changeset_comments.py --- a/rhodecode/tests/functional/test_changeset_comments.py +++ b/rhodecode/tests/functional/test_changeset_comments.py @@ -40,8 +40,8 @@ class TestChangeSetCommentsController(Te repo_name=HG_REPO, revision=rev)) # test DB self.assertEqual(ChangesetComment.query().count(), 1) - self.assertTrue('''
    %s ''' - '''comment(s) (0 inline)
    ''' % 1 in response.body) + response.mustcontain('''
    %s comment ''' + '''(0 inline)
    ''' % 1) self.assertEqual(Notification.query().count(), 1) self.assertEqual(ChangesetComment.query().count(), 1) @@ -76,7 +76,7 @@ class TestChangeSetCommentsController(Te #test DB self.assertEqual(ChangesetComment.query().count(), 1) response.mustcontain( - '''
    0 comment(s)''' + '''
    0 comments''' ''' (%s inline)
    ''' % 1 ) response.mustcontain( @@ -115,8 +115,8 @@ class TestChangeSetCommentsController(Te repo_name=HG_REPO, revision=rev)) # test DB self.assertEqual(ChangesetComment.query().count(), 1) - self.assertTrue('''
    %s ''' - '''comment(s) (0 inline)
    ''' % 1 in response.body) + response.mustcontain('''
    %s ''' + '''comment (0 inline)
    ''' % 1) self.assertEqual(Notification.query().count(), 2) users = [x.user.username for x in UserNotification.query().all()] @@ -148,5 +148,5 @@ class TestChangeSetCommentsController(Te response = self.app.get(url(controller='changeset', action='index', repo_name=HG_REPO, revision=rev)) - self.assertTrue('''
    0 comment(s)''' - ''' (0 inline)
    ''' in response.body) + response.mustcontain('''
    0 comments''' + ''' (0 inline)
    ''') diff --git a/rhodecode/tests/functional/test_compare.py b/rhodecode/tests/functional/test_compare.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/functional/test_compare.py @@ -0,0 +1,189 @@ +from rhodecode.tests import * +from rhodecode.model.repo import RepoModel +from rhodecode.model.meta import Session +from rhodecode.model.db import Repository +from rhodecode.model.scm import ScmModel +from rhodecode.lib.vcs.backends.base import EmptyChangeset + + +class TestCompareController(TestController): + + def test_index_tag(self): + self.log_user() + tag1 = '0.1.3' + tag2 = '0.1.2' + response = self.app.get(url(controller='compare', action='index', + repo_name=HG_REPO, + org_ref_type="tag", + org_ref=tag1, + other_ref_type="tag", + other_ref=tag2, + )) + response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2)) + ## outgoing changesets between tags + response.mustcontain('''r120:17544fbfcd33''' % HG_REPO) + response.mustcontain('''r119:36e0fc9d2808''' % HG_REPO) + response.mustcontain('''r118:bb1a3ab98cc4''' % HG_REPO) + response.mustcontain('''r117:41fda979f02f''' % HG_REPO) + response.mustcontain('''r116:9749bfbfc0d2''' % HG_REPO) + response.mustcontain('''r115:70d4cef8a376''' % HG_REPO) + response.mustcontain('''r112:c5ddebc06eaa''' % HG_REPO) + + ## files diff + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + response.mustcontain('''''' % (HG_REPO, tag1, tag2)) + + def test_index_branch(self): + self.log_user() + response = self.app.get(url(controller='compare', action='index', + repo_name=HG_REPO, + org_ref_type="branch", + org_ref='default', + other_ref_type="branch", + other_ref='default', + )) + + response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO)) + # branch are equal + response.mustcontain('
    No changesets
    !LWOKAi${&M-`aXH2ZTBzLUkrBXXAR;>^fj)mc&r?~cV>!_G`FM8u z1Ha?%BTtHGeVUrsprD{~WXMp%ws8=#Awht6vfvO@*)3+e
  • T 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Ì}YH#Sp1y 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 z
    LGOMMaa7V}8ceE_og+TDFyB`!E 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